理解TypeScript中的一個(gè)(偽)黑魔法
最近在學(xué)習(xí)TypeScript的時(shí)候,看到高級(jí)類型這里,對(duì)
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']);
這段代碼有點(diǎn)不太理解,感覺像是黑魔法一般咒唆,主要的疑惑點(diǎn)在于:
let strings: string[] = pluck(person, ['name']);
這段代碼,傳入給pluck的應(yīng)該是一個(gè)string[]内颗,那么泛型函數(shù)
function pluck<T, K extends keyof T>(o: T, names: K[]): TK;
這個(gè)簽名應(yīng)該被替換為钧排,
function pluck<Person, string extends keyof Person>(o: Person, names: string[]): Person[string][];
其中
keyof Person
等價(jià)于
'name' | 'age'
于是對(duì)于約束:
string extends 'name' | 'age'
而言,就是絕對(duì)通不過靜態(tài)類型檢查的均澳,但是在這里卻是能夠通過的恨溜,這讓我百思不得其解符衔。
后來我懷疑自己看漏了什么重要的東西,于是把文檔往上翻糟袁,在字符串字面量類型這里判族,看到了一點(diǎn)端倪,于是我在TypeScript中做了這樣子的嘗試:
type MyType = 'myType';
function bar(param : MyType[]) : void {
console.log(param);
}
bar(['myType']);
結(jié)果是輸出了:
["myType"]
于是我恍然大悟项戴,在前面的代碼中TypeScript的類型約束事實(shí)上是做了這樣子的工作:
-
通過
keyof Person
獲得了
"name" | "age"
-
根據(jù)
K extends keyof Person
約束了K要么是“name”要么是“age”**
假設(shè)如果兩者都不是形帮,那么靜態(tài)檢查將會(huì)直接報(bào)錯(cuò);
-
現(xiàn)在靜態(tài)檢查器來檢查
names : K[]
由于傳入的是['name']周叮,那么首先如果將其解釋為string[]的話顯然是不對(duì)的辩撑,那么進(jìn)一步地,TypeScript靜態(tài)檢查器嘗試將['name']中的'name'看成是一個(gè)字符字面量類型仿耽,然后將其解釋為'name'[]合冀,即字符字面量類型數(shù)組,這樣一來项贺,泛型變量K就是'name'君躺,約束'name' extends 'name' | 'age'就順利通過了。**
進(jìn)一步的开缎,T[K]事實(shí)上就是Person['name']棕叫,而Person['name']就是string所以這個(gè)函數(shù)的返回值就是string[]。
為了驗(yàn)證我的猜想奕删,我把最后那行調(diào)用改成了
let strings: string[] = pluck(person, ['name', 'name', 'name']);
然后測試的時(shí)候輸出:
["Jarid", "Jarid", "Jarid"]
這符合我的理解俺泣。
總結(jié)一下,其實(shí)理解這個(gè)問題最大的關(guān)鍵就是理解TypeScript的字符串字面量類型這個(gè)概念急侥,這個(gè)概念使得TypeScript的靜態(tài)檢查變得相當(dāng)靈活砌滞,從上面的描述我們可以看出'name'這個(gè)字符串使得TypeScript靜態(tài)分析的時(shí)候做了一定的推斷(可能推斷過程和我的描述并非一致侮邀,因?yàn)槲疫€沒有仔細(xì)研讀過TypeScript的源代碼)坏怪,這使得我們在閱讀TypeScript源代碼的時(shí)候需要額外地注意這樣一些靈活之處。