如何修复类型 'string' 无法分配给类型 'T[keyof T]'
How to fix Type 'string' is not assignable to type 'T[keyof T]'
给定以下 Typescript 函数:
const setter = <T = Record<string, string>>(obj: T, prop: keyof T, val: string): void => {
obj[prop] = val;
};
我的 IDE 出现以下错误:
Type 'string' is not assignable to type 'T[keyof T]'
如果T是一个字符串键和字符串值的Record,prop
是T的一个键,那么我本来以为可以给T[keyof T]
赋一个字符串。
如何正确使用泛型来修复此错误?
的直接问题
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
推断任何内容的(不太可能)事件中的 generic type parameter T
can be absolutely anything the caller of the function wants it to be. All you've done with T = Record<string, string>
is specified that T
would default 到 Record<string, string>
。但是 T
将由传入的任何值推断为 obj
,如果调用者未手动指定:
setter({ a: 1 }, "a", "oops"); // no compiler error
所以这不是你想说的。
也许您试图 constrain T
到 Record<string, string>
而不是默认设置。用 extends
表示,而不是 =
:
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
属性 的情况下进行完全错误的调用::
setter({ a: 1 }, "a", "oops"); // error!
// ----> ~ // number is not assignable to string
但是像这样的约束的问题是调用者可以 将 属性缩小到比 string
更具体的东西,例如 union of string literal types:
type Foo = { b: "x" | "y" };
const foo: Foo = { b: "x" };
badSetter2(foo, "b", "oopsie"); // no compiler error
这里,foo
是 Foo
类型的对象类型,其 b
属性 必须是 "x"
或 "y"
。我们显然已经将 "oopsie"
分配给它的 b
属性,这是不允许的。
对于您的预期用例,这种缩小可能不太可能发生,但编译器会关注它,因此错误仍然存在。您不一定可以将 string
分配给 obj[prop]
。
解决此问题的一种方法是确保 val
的类型可以分配给 prop
处的特定键。这涉及为键添加类型参数:
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]
(当你 index into 类型为 T
的对象时得到的类型K
类型的键),则 val
可被视为可分配给 obj[prop]
。并且正确地拒绝了从上面对 setter()
的无效调用。
这可能是执行此操作的“正确”方法。
您可以通过完全删除 T
来稍微简化它(以牺牲一点正确性为代价)。您只关心 obj
在 prop
属性 处有一个 string
值键,然后您可以离开 K
并改写如下:
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
,您可以将其设为 non-generic 函数:
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
这在假阴性方面并不比前一个差,但可能有更多的假阳性,我不会深入讨论,因为这个答案已经太长了。
好了。您有一系列解决方案可供选择,从完全通用到完全 non-generic,根据您的用例,其中任何一个都可能或多或少有用。
给定以下 Typescript 函数:
const setter = <T = Record<string, string>>(obj: T, prop: keyof T, val: string): void => {
obj[prop] = val;
};
我的 IDE 出现以下错误:
Type 'string' is not assignable to type 'T[keyof T]'
如果T是一个字符串键和字符串值的Record,prop
是T的一个键,那么我本来以为可以给T[keyof T]
赋一个字符串。
如何正确使用泛型来修复此错误?
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
推断任何内容的(不太可能)事件中的 generic type parameter T
can be absolutely anything the caller of the function wants it to be. All you've done with T = Record<string, string>
is specified that T
would default 到 Record<string, string>
。但是 T
将由传入的任何值推断为 obj
,如果调用者未手动指定:
setter({ a: 1 }, "a", "oops"); // no compiler error
所以这不是你想说的。
也许您试图 constrain T
到 Record<string, string>
而不是默认设置。用 extends
表示,而不是 =
:
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
属性 的情况下进行完全错误的调用::
setter({ a: 1 }, "a", "oops"); // error!
// ----> ~ // number is not assignable to string
但是像这样的约束的问题是调用者可以 将 属性缩小到比 string
更具体的东西,例如 union of string literal types:
type Foo = { b: "x" | "y" };
const foo: Foo = { b: "x" };
badSetter2(foo, "b", "oopsie"); // no compiler error
这里,foo
是 Foo
类型的对象类型,其 b
属性 必须是 "x"
或 "y"
。我们显然已经将 "oopsie"
分配给它的 b
属性,这是不允许的。
对于您的预期用例,这种缩小可能不太可能发生,但编译器会关注它,因此错误仍然存在。您不一定可以将 string
分配给 obj[prop]
。
解决此问题的一种方法是确保 val
的类型可以分配给 prop
处的特定键。这涉及为键添加类型参数:
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]
(当你 index into 类型为 T
的对象时得到的类型K
类型的键),则 val
可被视为可分配给 obj[prop]
。并且正确地拒绝了从上面对 setter()
的无效调用。
这可能是执行此操作的“正确”方法。
您可以通过完全删除 T
来稍微简化它(以牺牲一点正确性为代价)。您只关心 obj
在 prop
属性 处有一个 string
值键,然后您可以离开 K
并改写如下:
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
,您可以将其设为 non-generic 函数:
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
这在假阴性方面并不比前一个差,但可能有更多的假阳性,我不会深入讨论,因为这个答案已经太长了。
好了。您有一系列解决方案可供选择,从完全通用到完全 non-generic,根据您的用例,其中任何一个都可能或多或少有用。