理解TypeScript中的一个(伪)黑魔法
最近在学习TypeScript的时候,看到高级类型这里,对
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
return names.map(n => o[n]);
}
interface Person {
name: string;
age: number;
}
let person: Person = {
name: 'Jarid',
age: 35
};
let strings: string[] = pluck(person, ['name']);
这段代码有点不太理解,感觉像是黑魔法一般,主要的疑惑点在于:
let strings: string[] = pluck(person, ['name']);
这段代码,传入给pluck的应该是一个string[],那么泛型函数
function pluck<T, K extends keyof T>(o: T, names: K[]): TK;
这个签名应该被替换为,
function pluck<Person, string extends keyof Person>(o: Person, names: string[]): Person[string][];
其中
keyof Person
等价于
'name' | 'age'
于是对于约束:
string extends 'name' | 'age'
而言,就是绝对通不过静态类型检查的,但是在这里却是能够通过的,这让我百思不得其解。
后来我怀疑自己看漏了什么重要的东西,于是把文档往上翻,在字符串字面量类型这里,看到了一点端倪,于是我在TypeScript中做了这样子的尝试:
type MyType = 'myType';
function bar(param : MyType[]) : void {
console.log(param);
}
bar(['myType']);
结果是输出了:
["myType"]
于是我恍然大悟,在前面的代码中TypeScript的类型约束事实上是做了这样子的工作:
-
通过
keyof Person
获得了
"name" | "age"
-
根据
K extends keyof Person
约束了K要么是“name”要么是“age”**
假设如果两者都不是,那么静态检查将会直接报错;
-
现在静态检查器来检查
names : K[]
由于传入的是['name'],那么首先如果将其解释为string[]的话显然是不对的,那么进一步地,TypeScript静态检查器尝试将['name']中的'name'看成是一个字符字面量类型,然后将其解释为'name'[],即字符字面量类型数组,这样一来,泛型变量K就是'name',约束'name' extends 'name' | 'age'就顺利通过了。**
-
进一步的,T[K]事实上就是Person['name'],而Person['name']就是string所以这个函数的返回值就是string[]。
为了验证我的猜想,我把最后那行调用改成了
let strings: string[] = pluck(person, ['name', 'name', 'name']);
然后测试的时候输出:
["Jarid", "Jarid", "Jarid"]
这符合我的理解。
总结一下,其实理解这个问题最大的关键就是理解TypeScript的字符串字面量类型这个概念,这个概念使得TypeScript的静态检查变得相当灵活,从上面的描述我们可以看出'name'这个字符串使得TypeScript静态分析的时候做了一定的推断(可能推断过程和我的描述并非一致,因为我还没有仔细研读过TypeScript的源代码),这使得我们在阅读TypeScript源代码的时候需要额外地注意这样一些灵活之处。
网友评论