首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何修复类型“string”不能指定键入'T[keyof T]‘

如何修复类型“string”不能指定键入'T[keyof T]‘
EN

Stack Overflow用户
提问于 2022-04-27 00:28:56
回答 1查看 2.5K关注 0票数 6

具有下列打字本功能:

代码语言:javascript
复制
const setter = <T = Record<string, string>>(obj: T, prop: keyof T, val: string): void => {
    obj[prop] = val;
};

我从IDE中得到以下错误:

代码语言:javascript
复制
Type 'string' is not assignable to type 'T[keyof T]'

如果T是字符串键和字符串值的记录,而prop是T的键,那么我最初假设可以将字符串分配给T[keyof T]

如何正确使用泛型来修复此错误?

EN

Stack Overflow用户

回答已采纳

发布于 2022-04-27 02:15:20

眼前的问题

代码语言:javascript
复制
const setter = <T = Record<string, string>>(
  obj: T, prop: keyof T, val: string
): void => {
  obj[prop] = val; // error! 'string' is not assignable to 'T[keyof T]'
};

通用类型参数T绝对可以是函数调用方希望它成为的任何东西。您对T = Record<string, string>所做的一切都是指定的,在编译器无法为T推断任何事情的(不太可能的)事件中,Thttps://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#generic-parameter-defaultsRecord<string, string>。但是,如果调用方没有手动指定T,则无论传入哪个值为obj,都将推断出obj

代码语言:javascript
复制
setter({ a: 1 }, "a", "oops"); // no compiler error

所以这不是你想说的。

也许您是在尝试https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints T to Record<string, string>,而不是默认使用它。用extends来表示,而不是用=表示。

代码语言:javascript
复制
const setter = <T extends Record<string, string>>(
  obj: T, prop: keyof T, val: string
): void => {
  obj[prop] = val; // Type 'string' is not assignable to type 'T[keyof T]'
};

如果obj在相关键上有一个非string属性,那么至少可以防止人们发出完全不正确的呼叫::

代码语言:javascript
复制
setter({ a: 1 }, "a", "oops"); // error!
// ----> ~ // number is not assignable to string

但是,像这样的约束的问题是,调用方可以将属性缩小到比string更具体的内容,例如字符串文字类型友联市

代码语言:javascript
复制
type Foo = { b: "x" | "y" };
const foo: Foo = { b: "x" };
badSetter2(foo, "b", "oopsie"); // no compiler error

在这里,fooFoo类型,它是一个对象类型,其b属性必须是"x""y"。显然,我们已经将"oopsie"分配给它的b属性,这是不应该允许的。

对于您预期的用例来说,这种缩小可能不太可能,但是编译器会关注它,因此错误仍然存在。您不一定要将string分配给obj[prop]

解决这一问题的一种方法是确保val的类型可以分配给prop上的特定键。这涉及为键添加一个类型参数:

代码语言:javascript
复制
const setter = <T extends Record<string, string>, K extends keyof T>(
  obj: T, prop: K, val: T[K]
): void => {
  obj[prop] = val; // okay
};

setter({ a: 1 }, "a", "oops"); // compiler error
setter(foo, "b", "oopsie"); // compiler error
setter({ z: "hello" }, "z", "goodbye"); // okay

现在一切都按要求运作。使用prop作为K类型,val作为类型T[K] (当您使用K类型的键对一个T类型的对象进行索引成时得到的类型),那么val就可以被看作是可分配给obj[prop]的。上面对setter()的无效调用也被正确地拒绝了。

这可能是做这件事的“正确”方式。

您可以通过完全删除T来简化它(以牺牲一点正确性为代价)。您所关心的只是objprop属性上有一个string值的键,然后您可以离开K并重新定义如下:

代码语言:javascript
复制
const setter = <K extends PropertyKey>(
  obj: Record<K, string>, prop: K, val: string
): void => {
  obj[prop] = val; // okay
};

setter({ a: 1 }, "a", "oops"); // compiler error
setter(foo, "b", "oopsie"); // okay?!
setter({ z: "hello" }, "z", "goodbye"); // okay

这再次防止了疯狂的调用,但仍然允许错误的调用,比如将foo.b设置为"oopsie"

最后,您可以进一步简化它;如果您甚至不关心特定的键类型K,您只需将它变成一个非泛型函数:

代码语言:javascript
复制
const setter = (
  obj: Record<string, string>, prop: string, val: string
): void => {
  obj[prop] = val;
};

setter({ a: 1 }, "a", "oops"); // compiler error
setter(foo, "b", "oopsie"); // okay?!
setter({ z: "hello" }, "z", "goodbye"); // okay

这并不比前一个错误的否定更糟,但可能有更多的错误,我不会进入,因为这个答案已经太长了。

那你就去吧。您可以选择从完全泛型到完全非泛型的各种解决方案,其中任何一种解决方案都可能或多或少地有用,这取决于您的用例。

操场链接到代码

票数 8
EN
查看全部 1 条回答
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72021963

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档