Ang*_*eno 14 typescript typescript-generics
给定以下 Typescript 函数:
const setter = <T = Record<string, string>>(obj: T, prop: keyof T, val: string): void => {
obj[prop] = val;
};
Run Code Online (Sandbox Code Playgroud)
我从 IDE 中收到以下错误:
Type 'string' is not assignable to type 'T[keyof T]'
Run Code Online (Sandbox Code Playgroud)
如果 T 是字符串键和字符串值的记录,并且prop是 T 的键,那么我最初假设我可以将字符串分配给T[keyof T]。
正确使用泛型来修复此错误的方法是什么?
jca*_*alz 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]'
};
Run Code Online (Sandbox Code Playgroud)
是泛型类型参数T绝对可以是函数调用者想要的任何东西。您所做的一切T = Record<string, string>都是指定的,在T编译器无法推断. 但如果调用者未手动指定,则将通过传入的任何值来推断:Record<string, string>TTobj
setter({ a: 1 }, "a", "oops"); // no compiler error
Run Code Online (Sandbox Code Playgroud)
所以这不是你想说的。
也许你试图限制 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]'
};
Run Code Online (Sandbox Code Playgroud)
这至少可以防止人们在相关键上obj有非属性的情况下做出完全错误的调用::string
setter({ a: 1 }, "a", "oops"); // error!
// ----> ~ // number is not assignable to string
Run Code Online (Sandbox Code Playgroud)
但是像这样的约束的问题是调用者可以将属性缩小到比 更具体的东西string,例如字符串文字类型的联合:
type Foo = { b: "x" | "y" };
const foo: Foo = { b: "x" };
badSetter2(foo, "b", "oopsie"); // no compiler error
Run Code Online (Sandbox Code Playgroud)
这里,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
Run Code Online (Sandbox Code Playgroud)
现在一切都按预期进行。使用propas typeK和valas type T[K](当您索引到T具有类型键的类型的对象时获得的类型K),那么val可以被视为可分配给obj[prop]。setter()上面的无效调用被正确拒绝。
这可能是执行此操作的“正确”方法。
您可以通过完全删除来稍微简化它(以牺牲一点正确性为代价)T。您所关心的只是该房产obj有一把string有价值的钥匙prop,然后您可以离开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
Run Code Online (Sandbox Code Playgroud)
这再次防止了疯狂的调用,但仍然允许错误的调用,例如设置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
Run Code Online (Sandbox Code Playgroud)
就误报而言,这并不比前一个更糟糕,但可能会有更多误报,我不会讨论这一点,因为这个答案已经太长了。
所以就这样吧。您有一系列解决方案可供选择,从完全通用到完全非通用,其中任何一个都可能或多或少有用,具体取决于您的用例。
| 归档时间: |
|
| 查看次数: |
11047 次 |
| 最近记录: |