假设我有一个为一组数据定义有效值的接口:
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<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
} ~~~~~
}
“ bar”类型的参数不能分配给“ Extract
”类型的参数。 ts(2345)
还值得注意的是,在第二种情况下,如果直接使用Foo
而不是使用扩展的泛型,则不会引发任何错误:
class ExtendedGetter extends Getter<Foo> { ... }
为什么会这样?
Extract<keyof T, string>
和keyof T
之间引起错误的区别是什么?
答案 0 :(得分:1)
该行为似乎被视为错误(请参见microsoft/TypeScript#24560),但我看不到任何迹象表明它将在不久的将来得到修复。
但是我倾向于将其归类为无法将值分配给未解析的conditional types的编译器类别。如果您有诸如T extends U ? X : Y
之类的条件类型,并且T
或U
是未解析的泛型类型或依赖于未解析的泛型类型,则编译器不会做太多分析来验证是否有某些值可分配给它;它只是拒绝任务:
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
分配给这些类型的变量是安全的,至少从TypeScript 3.6开始。我看到a pull request可能会改善此情况,并且可能会解决您的代码,但是我只是在推测,不知道何时或是否会将其转化为语言。
只要Extract<keyof T, string>
是一个未解决的泛型,T
就很可能使编译器难以推理(因为Extract
utility type是implemented条件类型)。请注意,一旦将T
解析为Foo
之类的具体类型,然后编译器将Extract<keyof T, string>
评估为具体类型"bar"
,那么就没有问题了,因为您看到了。
因此,解决方法。正如您指出的,您可以做的一件事就是只使用keyof T
而不是Extract<keyof T, string>
。尽管keyof T
类型是通用的,但仍可从"bar"
分配类型……编译器能够对未解析的通用类型进行 some 推理;如果类型是有条件的,这样做会更糟。如果这对您有用,那就太好了。但是,如果您想使用Extract<keyof T, string>
...
我会使用type assertion。当您对编译器不了解的值的类型有所了解时,类型断言很有用。在这种情况下,您将确保"bar"
可分配给Extract<keyof T, string>
,因为"bar"
可同时分配给string
和keyof T
。面对现实,您比编译器更聪明...而且类型断言是吹嘘自己卓越智能的好方法:
class ExtendedGetter<T extends Foo> extends Getter<T> {
use() {
this.get("bar" as Extract<keyof T, string>); // I'm smarter than the compiler ?
}
}
类型断言当然应该谨慎使用,因为如果您对断言有误并且对编译器撒谎,则在运行时可能会遇到一些令人不愉快的意外。但是在这种情况下,您可以确定该断言始终有效。
好的,希望能有所帮助。祝你好运!