【问题标题】:What is the difference between `keyof T` and `Extract<keyof T, string>` when using extended generics?使用扩展泛型时,`keyof T` 和 `Extract<keyof T, string>` 有什么区别?
【发布时间】:2020-01-02 21:45:54
【问题描述】:

假设我有一个为一组数据定义有效值的接口:

interface Foo {
  bar: boolean;
}

而且我希望一个类能够使用方法公开该数据。我发现如果我使用keyof T 来定义键,它工作正常:

abstract class Getter<T> {
  private data: T;

  get<K extends keyof T>(key: K): T[K] {
    return this.data[key];
  }

  abstract use(): void;
}

class ExtendedGetter<T extends Foo> extends Getter<T> {
  use() {
    this.get('bar'); // OK
  }
}

但是,将键限制为仅接受带有 Extract&lt;keyof T, string&gt; 的字符串会导致错误:

abstract class Getter<T> {
  private data: T;

  get<K extends Extract<keyof T, string>>(key: K): T[K] {
    return this.data[key];
  }

  abstract use(): void;
}

class ExtendedGetter<T extends Foo> extends Getter<T> {
  use() {
    this.get('bar'); // ERROR
  }          ~~~~~
}

'"bar"' 类型的参数不能分配给'Extract' 类型的参数。 ts(2345)

还值得注意的是,在第二种情况下,如果直接使用Foo而不是使用扩展泛型,则不会引发错误:

class ExtendedGetter extends Getter<Foo> { ... }

为什么会这样?

导致错误的Extract&lt;keyof T, string&gt;keyof T 有什么区别?

【问题讨论】:

  • 答案可能是“编译器无法识别很多可分配给未解析条件类型的内容”(如果 T 是泛型类型参数而不是具体类型)。不过,我很想找到一份规范的文档来说明这一点。
  • 似乎被认为是一个错误(microsoft/TypeScript#24560
  • @jcalz 如果您将其作为答案提交,我会接受。我会一直使用keyof T,直到该错误得到解决,假设曾经发生过。

标签: typescript


【解决方案1】:

看起来这种行为被认为是一个错误(请参阅microsoft/TypeScript#24560),但我没有看到任何迹象表明它会在不久的将来得到修复。

但我倾向于将此归为编译器无法为未解析的conditional types 赋值的类别。如果您有像T extends U ? X : Y 这样的条件类型,并且TU 是未解析的泛型类型或依赖于未解析的泛型类型,那么编译器不会进行太多分析来验证是否可以为其分配某个值;它大多只是拒绝分配:

function unresolved<T extends string>() {
  const x: [T] extends [string] ? number : number = 1; // error!
  const y: string extends T ? number : number = 1; // error!  
}

在这种情况下,即使两种条件类型几乎必须评估为 number,编译器也无法判断将 1 分配给这些类型的变量是安全的,至少截至目前打字稿 3.6。我看到 a pull request 可能会改进这一点,并且可能会解决您的代码,但我只是在推测,我不知道它何时或是否会成为语言。

只要说Extract&lt;keyof T, string&gt;T 是未解析的泛型时,编译器可能很难推理(因为Extract utility typeimplemented 作为条件类型)。请注意,一旦T 被解析为具体类型,例如Foo,那么编译器会将Extract&lt;keyof T, string&gt; 评估为具体类型"bar",并且没有问题,正如您所看到的。


所以,解决方法。正如您所指出的,您可以做的一件事就是使用keyof T 而不是Extract&lt;keyof T, string&gt;。已知类型 keyof T 可以从 "bar" 分配,尽管它是泛型的......编译器能够对未解析的泛型类型进行一些推理;当类型是有条件的时,这样做会更糟。如果这对你有用,那就太好了。但是如果你想使用Extract&lt;keyof T, string&gt;...

我会使用type assertion。当您知道编译器不知道的值的类型时,类型断言很有用。在这种情况下,您可以确定 "bar" 将可分配给 Extract&lt;keyof T, string&gt;,因为 "bar" 可分配给 stringkeyof T。面对现实吧,你比编译器更聪明......类型断言是你吹嘘自己卓越智能的好方法:

class ExtendedGetter<T extends Foo> extends Getter<T> {
  use() {
    this.get("bar" as Extract<keyof T, string>); // I'm smarter than the compiler ?
  }
}

当然,应该谨慎使用类型断言,因为如果你的断言有误并且对编译器撒谎,那么你可能会在运行时遇到一些令人不快的意外。但在这种情况下,您可以非常确定断言始终有效。


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

Link to code

【讨论】:

    猜你喜欢
    • 2019-08-05
    • 1970-01-01
    • 2019-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多