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

时间:2019-07-07 20:27:15

标签: typescript typescript-typings typescript-generics

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

但是它无法使用Type '1' is not assignable to type 'T[Extract<keyof T, string>]'.进行编译。我以为T[Extract<keyof T, string>]number,所以分配1的{​​{1}}应该可以。

我的代码有什么问题?

number

1 个答案:

答案 0 :(得分:1)

编译器通常不会对泛型类型(即依赖于T内的func()之类的未解析类型参数的类型)进行非常复杂的操作分析...以更好地处理更简单的具体类型(例如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,这些属性都不等于1。当我们调用func(bar)时,推断TBar,因此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