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

Type '1' is not assignable to type 'T[Extract<keyof T, string>]'

我有一个以下函数,它接受一个扩展 Foo(它是一个对象)的类型 T 的参数。在函数中,它遍历给定对象的每个键以创建一个具有完全相同键但对应值均为 1 的新对象(这个函数做什么并不重要)。

但是使用Type '1' is not assignable to type 'T[Extract<keyof T, string>]'.编译失败。我认为 T[Extract<keyof T, string>]number,所以分配 1number 应该可行。

我的代码有什么问题?

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

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

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

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

const concreteFunc = (obj: Foo): Foo => {
  for (const name in obj) {
    obj[name] = 1; // okay
  }
  return obj; // okay
};

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

这种情况经常发生在泛型函数的实现内部。如果你仔细分析你在做什么并确定它确实是类型安全的,你可以使用 type assertions 来删除警告。

例如,您可以这样做:

const func = <T extends Foo>(obj: T): T => {
  for (const name in obj) {
    obj[name] = 1 as T[typeof name]; // assert BUT BEWARE ☠
  }
  return obj;
};

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

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

interface Bar extends Foo {
  two: 2;
  four: 4;
  six: 6;
  eight: 8;
}

const bar: Bar = {
  two: 2,
  four: 4,
  six: 6,
  eight: 8
};

const b = func(bar);

console.log(b.two); // 2 at compile time, but prints 1!
console.log(b.four); // 4 at compile time, but prints 1!
console.log(b.six); // 4 at compile time, but prints 1!
console.log(b.eight); // 4 at compile time, but prints 1!

这里我们看到一个接口 Bar,它通过添加值为 numeric literals 的已知属性扩展 Foo,其中 none 等于 1 .当我们调用 func(bar) 时,T 被推断为 Bar,因此 func(bar) 的输出也应该是 Bar.

坏事发生了。我们有一个对象,其已知属性在编译时应该是偶数,但实际上在运行时是 1

所以这就是为什么您可能不应该在像 func() 这样的函数中使用断言的原因。可能有一种真正安全的方式来编写 func()... 比如,可能是这样的:

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

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

funcSafer(bar); // error! property "two" is incompatible
const foo: Foo = {two: 2, four: 4}; // just Foo, not Bar
funcSafer(foo); // okay
funcSafer({a: 1 as 1}); // okay
funcSafer({a: 4}); // okay, interpreted as {a: number}
funcSafer({a: 4 as 4}); // error, "a" is incompatible

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

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

Link to code