简体   繁体   English

`keyof T`和`Extract有什么区别 <keyof T, string> `使用扩展泛型时?

[英]What is the difference between `keyof T` and `Extract<keyof T, string>` when using extended generics?

Let's say I have an interface that defines valid values for a set of data: 假设我有一个接口,该接口定义了一组数据的有效值:

interface Foo {
  bar: boolean;
}

And I want a class to be able to expose that data with a method. 我希望一个类能够使用一种方法公开该数据。 I'm finding that it works fine if I use keyof T to define the keys: 我发现如果我使用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
  }
}

However, restricting the keys to only accept strings with Extract<keyof T, string> causes an error: 但是,将键限制为仅接受带有Extract<keyof T, string>会导致错误:

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
  }          ~~~~~
}

Argument of type '"bar"' is not assignable to parameter of type 'Extract<keyof T, string>'. 类型““ bar””的参数不能分配给类型“ Extract <keyof T,string>”的参数。 ts(2345) TS(2345)

It's also worth noting that, in the second scenario, no error is thrown if Foo is used directly instead of using an extended generic: 还值得注意的是,在第二种情况下,如果直接使用Foo而不是使用扩展的泛型,则不会引发任何错误:

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

Why does this happen? 为什么会这样?

What is the difference between Extract<keyof T, string> and keyof T that causes the error? 导致错误的Extract<keyof T, string>keyof T有什么区别?

It looks like this behavior is considered to be a bug (see microsoft/TypeScript#24560 ) but I don't see anything indicating it will be fixed in the near future. 看来此行为被认为是一个错误(请参阅microsoft / TypeScript#24560 ),但是我看不到任何迹象表明它将在不久的将来得到修复。

But I would tend to lump this into the category of the compiler being unable to assign values to unresolved conditional types . 但是我倾向于将其归入无法将值分配给未解决的条件类型的编译器类别 If you have a conditional type like T extends U ? X : Y 如果您有条件类型,例如T extends U ? X : Y T extends U ? X : Y and either T or U are unresolved generic types or dependent on unresolved generic types, then the compiler doesn't do much analysis to verify if some value is assignable to it; T extends U ? X : YTU是不可解析的泛型类型或依赖于不可解析的泛型类型,因此编译器不会做太多分析来验证是否可以为其分配一些值; it mostly just rejects the assignment: 它通常只是拒绝分配:

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

In that case, even though both conditional types pretty much must evaluate to number , the compiler cannot tell that it's safe to assign 1 to variables of those types, at least as of TypeScript 3.6. 在这种情况下,即使两个条件类型非常的值必须number ,编译器不能告诉大家,它是安全的指定1到这些类型的变量,至少打字稿3.6。 I see a pull request which might improve this, and possibly it would address your code, but I'm just speculating and I don't know when or if it will make it into the language. 我看到一个拉取请求可能会改善此情况,并且可能会解决您的代码,但是我只是在推测,我不知道何时或是否会将其转化为语言。

Suffice it to say that Extract<keyof T, string> when T is an unresolved generic is likely to be hard for the compiler to reason about (since the Extract utility type is implemented as a conditional type). 可以肯定地说,当T是一个未解决的泛型时, Extract<keyof T, string>可能很难使编译器对此进行推理(因为Extract实用程序类型实现为条件类型)。 Note that once T is resolved to a concrete type like, Foo , then Extract<keyof T, string> is evaluated by the compiler to the concrete type "bar" and there is no problem, as you saw. 请注意,一旦将T解析为Foo类的具体类型,然后编译器Extract<keyof T, string>评估为具体类型"bar" ,就不会出现问题。


So, workarounds. 因此,解决方法。 One thing you can do, as you noted, is to just use keyof T instead of Extract<keyof T, string> . 正如您指出的,您可以做的一件事就是只使用keyof T而不是Extract<keyof T, string> The type keyof T is known to be assignable from "bar" , despite being generic... the compiler is able to do some reasoning about unresolved generic types; 已知keyof T的类型keyof T可以从"bar"分配,尽管它是泛型的……编译器能够对未解析的泛型进行某种推理; it's just much worse at doing so when the type is conditional. 如果类型是有条件的,这样做会更糟。 If that works for you, great. 如果这对您有用,那就太好了。 But if you want to use Extract<keyof T, string> ... 但是如果您想使用Extract<keyof T, string> ...

I would use a type assertion . 我将使用类型断言 Type assertions are useful when you know something about the type of a value that the compiler doesn't know. 当您对编译器不了解的值的类型有所了解时,类型断言很有用。 In this case, you are sure that "bar" will be assignable to Extract<keyof T, string> , since "bar" is assignable to both string and keyof T . 在这种情况下,由于"bar"可同时分配给stringkeyof T ,因此请确保"bar"可分配给Extract<keyof T, string> Face it, you're smarter than the compiler... and type assertions are a good way for you to brag about your superior intelligence: 面对现实,您比编译器更聪明...而且类型断言是吹嘘自己卓越智能的一种好方法:

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

Type assertions should be used with caution, of course, because if you're wrong about your assertion and lie to the compiler then you're likely to have some unpleasant surprises at runtime. 当然,应谨慎使用类型断言,因为如果您对断言有误并且对编译器撒谎,则在运行时可能会遇到一些令人不愉快的惊喜。 But in this case you can be pretty sure that the assertion is always valid. 但是在这种情况下,您可以确定该断言始终有效。


Okay, hope that helps. 好的,希望对您有所帮助。 Good luck! 祝好运!

Link to code 链接到代码

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM