类型“1”不可分配给类型“T[Extract<keyof T, string>]”

hit*_*han 7 typescript typescript-generics typescript-typings

我有一个以下函数,它采用扩展 Foo (它是一个对象)的 T 类型参数。在该函数中,它迭代给定对象的每个键,以创建一个具有完全相同键但相应值均为 1 的新对象(该函数执行的操作并不重要)。

但它无法通过Type '1' is not assignable to type 'T[Extract<keyof T, string>]'.. 我认为分配T[Extract<keyof T, string>]哪个 应该有效。number1number

我的代码有什么问题?

type Foo = {
  [key: string]: number
}

const func = <T extends Foo>(obj: T): T => {
  for (const name in obj) {
    obj[name] = 1
  }
  return obj
} 
Run Code Online (Sandbox Code Playgroud)

jca*_*alz 9

编译器通常不会对泛型类型的操作进行非常复杂的分析(即依赖于未解析的类型参数的类型,例如T在 的实现中func())...它往往更擅长处理具体类型(例如Foo)更简单。

\n\n

因此编译器非常满意并将允许您的函数使用以下具体版本:

\n\n
const concreteFunc = (obj: Foo): Foo => {\n  for (const name in obj) {\n    obj[name] = 1; // okay\n  }\n  return obj; // okay\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

由于尚不知道未解析的泛型类型,因此编译器将不太确定您正在做的事情是否安全,并且可能会发出警告。此警告并不一定意味着您肯定犯了错误。

\n\n

这种情况经常发生在泛型函数的实现中。如果您仔细分析您正在做的事情并确定它确实是类型安全的,则可以使用类型断言来删除警告。

\n\n

例如,您可以这样做:

\n\n
const func = <T extends Foo>(obj: T): T => {\n  for (const name in obj) {\n    obj[name] = 1 as T[typeof name]; // assert BUT BEWARE \xe2\x98\xa0\n  }\n  return obj;\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

但请注意,类型断言意味着类型安全的责任已从编译器转移到您身上......并且(回答您的问题)这是不安全的

\n\n

这就是为什么......考虑以下代码:

\n\n
interface Bar extends Foo {\n  two: 2;\n  four: 4;\n  six: 6;\n  eight: 8;\n}\n\nconst bar: Bar = {\n  two: 2,\n  four: 4,\n  six: 6,\n  eight: 8\n};\n\nconst b = func(bar);\n\nconsole.log(b.two); // 2 at compile time, but prints 1!\nconsole.log(b.four); // 4 at compile time, but prints 1!\nconsole.log(b.six); // 4 at compile time, but prints 1!\nconsole.log(b.eight); // 4 at compile time, but prints 1!\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这里,我们看到一个接口,它通过添加已知属性Bar来扩展,这些属性的值为数字文字,其中没有一个等于。当我们调用 时,被推断为,因此 的输出也应该是。Foo1func(bar)TBarfunc(bar)Bar

\n\n

糟糕的事情也会发生。我们有一个对象,其已知属性在编译时应该是偶数,但实际上是运行时的数字1

\n\n

这就是为什么您可能不应该在像func(). 可能有一种实际上安全的写法func()......比如,可能是这样的:

\n\n
const funcSafer = <\n  T extends { [K in keyof T]: 1 extends T[K] ? unknown : never }\n>(\n  obj: T\n): T => {\n  for (const name in obj) {\n    obj[name] = 1 // error! still need "as T[typeof name]"\n  }\n  return obj;\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里,具体的约束T1应该可分配给它的所有属性。这具有以下理想效果:

\n\n
funcSafer(bar); // error! property "two" is incompatible\nconst foo: Foo = {two: 2, four: 4}; // just Foo, not Bar\nfuncSafer(foo); // okay\nfuncSafer({a: 1 as 1}); // okay\nfuncSafer({a: 4}); // okay, interpreted as {a: number}\nfuncSafer({a: 4 as 4}); // error, "a" is incompatible\n
Run Code Online (Sandbox Code Playgroud)\n\n

但当然,编译器仍然无法判断obj[name] = 1实现内部是否安全。它太复杂了......所以我们需要断言。

\n\n

好的,希望有帮助。祝你好运!

\n\n

链接到代码

\n

  • 我们知道“obj[name]”可分配给“number”,但我们“不”知道“number”可分配给“obj[name]”。所以 `const x: number = obj[name]` 可以,但 `obj[name] = 1234` 不行,因为 `obj[name]` 的类型可能比 number 窄。这就是“Bar”示例所显示的内容。`bar.two` 的类型是 `2`,而不是 `number`。您可以安全地将“2”类型的值复制到“number”类型的变量中,但无法安全地将“number”类型的值复制到“2”类型的变量中。那有意义吗? (2认同)