具有下列打字本功能:
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是字符串键和字符串值的记录,而prop是T的键,那么我最初假设可以将字符串分配给T[keyof T]。
如何正确使用泛型来修复此错误?
发布于 2022-04-27 02:15:20
眼前的问题
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推断任何事情的(不太可能的)事件中,T将https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#generic-parameter-defaults到Record<string, string>。但是,如果调用方没有手动指定T,则无论传入哪个值为obj,都将推断出obj:
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来表示,而不是用=表示。
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更具体的内容,例如字符串文字类型的友联市
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] (当您使用K类型的键对一个T类型的对象进行索引成时得到的类型),那么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,您只需将它变成一个非泛型函数:
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这并不比前一个错误的否定更糟,但可能有更多的错误,我不会进入,因为这个答案已经太长了。
那你就去吧。您可以选择从完全泛型到完全非泛型的各种解决方案,其中任何一种解决方案都可能或多或少地有用,这取决于您的用例。
https://stackoverflow.com/questions/72021963
复制相似问题