TypeScript 類型編程: 從基礎(chǔ)到編譯器實戰(zhàn)
Typescript 的類型編程可以理解為一門有限的函數(shù)式編程語言。
本文假定讀者已經(jīng)使用過 typescript 并且了解基礎(chǔ)的類型概念,不會介紹基礎(chǔ)概念,主要專注于介紹如何進(jìn)行系統(tǒng)化的類型編程。示例主要來源于官網(wǎng)、類型挑戰(zhàn)倉庫以及日常開發(fā)。
一、類型編程基礎(chǔ)
既然稱作類型編程,那自然和普通編程語言一樣,用于類型變量定義語句、類型表達(dá)式、類型函數(shù)等等,本小結(jié)將詳細(xì)講述類型編程的一些基礎(chǔ)知識。
希望通過本文能夠幫助讀者更好的理解 TS 的類型,讓日常開發(fā)中的類型操作更加容易。
看大佬們用 ts 類型實現(xiàn)編譯器,看起來非常其實(確實厲害=_=),不過理解這篇文章的思想后,讀者們也可以實現(xiàn)~文章最后一個示例實現(xiàn)了一個 簡易加法表達(dá)式求值器。
1. 類型變量定義
TS 定義類型的方式有多種:
使用 type。 使用 interface。 使用 class、enum 等等,其中 class、enum 可以既為值,又為類型。
TS 提供了大量的基礎(chǔ)類型,可以直接在定義類型變量時使用(關(guān)于基礎(chǔ)類型的詳細(xì)介紹可以查閱 TS 文檔):
基礎(chǔ)數(shù)據(jù)類型,比如 string、number、boolean、symbol、undefined 等等。 字面量類型,比如 '123',5 等 對象類型,比如 { a: string } 函數(shù)類型,比如 (a: string) => void 元組類型,比如 [1, 2, 3] 數(shù)組類型,比如 string[] ...
//?使用?type?定義類型變量,類型是一個字面亮類型?'123'
type?TypeA?=?'123'
//?使用?interface?定義類型變量
interface?TypeB?{
??a:?string
}
//?將對象類型
//?{
//??b:?number
//??c:?TypeA
//?}?
//?賦值給?TypeC
//?TypeA?是上面定義的類型變量,可以直接使用
type?TypeC?=?{
??b:?number
??c:?TypeA
}
//?類型變量可以直接賦值給另一個類型變量
type?D?=?TypeB
//?將函數(shù)類型賦值給?E
type?E?=?(a:?string)?=>?void;
基于 TS 的基礎(chǔ)類型以及 type 等關(guān)鍵字,就可以定義自定義的類型變量。
2. 類型操作符
ts 中也定義了大量類型操作,例如 &(對象類型合并)、|(聯(lián)合類型)等等,這些操作可以操作 TS 的類型。
2.1 & - 合并類型對象
& 合并多個類型對象的鍵到一個類型對象中。
type?A?=?{?a:?number?}
type?B?=?{?b:?string?}
type?C?=?A?&?B;
//?C?包含?A?和?B?定義的所有鍵
/**
*?C?=?{
????a:?number;
????b:?string;
??}
*/
const?c:?C?=?{
??a:?1,
??b:?'1'
}
注意使用 & 時,兩個類型的鍵如果相同,但類型不同,會報錯:
type?A?=?{?a:?number?}
type?B?=?{?a:?string?}
type?C?=?A?&?B;
/**
報錯:
Type?'number'?is?not?assignable?to?type?'never'.(2322)
input.tsx(62,?3):?The?expected?type?comes?from?property?'a'?which?is?declared?here?on?type?'C'
(property)?a:?never
*/
const?c:?C?=?{
??a:?1?//?error
}
2.2 | - 聯(lián)合類型
|將多個類型組成聯(lián)合類型:
type?A?=?string?|?number;
type?B?=?string;
此時類型 A 既可以是 string 又可以是 number,類型 B 是類型 A 的子集,所有能賦值給類型 B 的值都可以賦值給類型 A。
2.3 keyof - 獲取對象類型的鍵
keyof 可以獲取某些對象類型的鍵:
interface?People?{
??a:?string;
??b:?string;
}
//?返回?'a'?|?'b'
type?KeyofPeople?=?keyof?People;
//?type?KeyofPeople?=?'a'?|?'b';
用這種方式可以獲取某個類型的所有鍵。
注意 keyof 只能對類型使用,如果想要對值使用,需要先使用 typeof 獲取類型。
2.4 typeof - 獲取值的類型
typeof 可以獲取值的類型。
//?獲取對象的類型
const?obj?=?{?a:?'123',?b:?123?}
type?Obj?=?typeof?obj;
/**
type?Obj?=?{
????a:?string;
????b:?number;
}
*/
//?獲取函數(shù)的類型
function?fn(a:?Obj,?b:?number)?{
??return?true;
}
type?Fn?=?typeof?fn;
/**
type?Fn?=?(a:?Obj,?b:?number)?=>?boolean
*/
//?...獲取各種值的類型
注意對于 enum 需要先進(jìn)行 typeof 操作獲取類型,才能通過 keyof 等類型操作完成正確的類型計算(因為 enum 可以是類型也可以是值,如果不使用 typeof 會當(dāng)值計算):
enum?E1?{
??A,
??B,
??C
}
type?TE1?=?keyof?E1;
/**
拿到的是錯誤的類型
type?TE1?=?"toString"?|?"toFixed"?|?"toExponential"?|?"toPrecision"?|?"valueOf"?|?"toLocaleString"
*/
type?TE2?=?keyof?typeof?E1;
/**
拿到的是正確的類型
type?TE2?=?"A"?|?"B"?|?"C"
*/
2.5 [...] - 元組展開與合并
元組可以視為長度確定的數(shù)組,元組中的每一項可以是任意類型。通過 [...元組, ...元組] 語法可以合并兩個元組。
結(jié)合元組展開以及 infer 類型推斷,可以實現(xiàn)類型中的數(shù)組操作,比如 pop(),后文介紹 infer 時將詳細(xì)介紹。
type?TupleA?=?[1,?2,?3]
type?TupleB?=?[...TupleA,?4]
/**
type?TupleB?=?[1,?2,?3,?4]
*/
type?TupleC?=?[0,?...TupleA]
/**
type?TupleC?=?[0,?1,?2,?3]
*/
2.6 [in] - 遍歷對象鍵值
在對象類型中,可以通過 [臨時類型變量 in 聯(lián)合類型] 語法來遍歷對象的鍵,示例如下:
//?下述示例遍歷?'1'?|?'2'?|?3'?三個值,然后依次賦值給?K,K?作為一個臨時的類型變量可以在后面直接使用?
/**
下述示例最終的計算結(jié)果是:
type?MyType?=?{
????1:?"1";
????2:?"2";
????3:?"3";
}
因為?K?類型變量的值在每次遍歷中依次是?'1',?'2',?'3'?所以每次遍歷時對象的鍵和值分別是?{?'1':?'2'?}?{?'2':?'2'?}?和?{?'3':?'3'?},
最終結(jié)果是這個三個結(jié)果取?&
*/
type?MyType?=?{
??//?注意能遍歷的類型只有?string、number、symbol,也就是對象鍵允許的類型
??[K?in?'1'?|?'2'?|?'3']:?K?
}
[in] 常常和 keyof 搭配使用,遍歷某一個對象的鍵,做相應(yīng)的計算后得到新的類型,如下:
type?Obj?=?{
??a:?string;
??b:?number;
}
/**
遍歷?Obj?的所有鍵,然后將所有鍵對應(yīng)的值的類型改成?boolean?|?K,返回結(jié)果如下:
type?MyObj?=?{
????a:?boolean?|?"a";
????b:?boolean?|?"b";
}
這樣我們就實現(xiàn)了給?Obj?的所有值的類型加上?|?boolean?的效果
*/
type?MyObj?=?{
??[K?in?keyof?Obj]:?boolean?|?K
}
in 后面還可以接 as,as 后面可以接類型表達(dá)式(文檔:https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as)
type?Getters?=?{
????[Property?in?keyof?Type?as?`get${Capitalize<string?&?Property>}`]:?()?=>?Type[Property]
};
3. 泛型 - 類型函數(shù)
TS 的泛型可以類比 Javascript 中的函數(shù)
3.1 定義泛型
使用
//?接口泛型
interface?Obj1?{
??a:?T
}
//?使用?type?也能定義泛型
type?Type1?=?{?b:?T?}
//?函數(shù)泛型
type?Fn1?=?(...args:?any[])?=>?any;
//?泛型也可以有默認(rèn)值,這樣如果沒有指定泛型參數(shù),默認(rèn)是?string
interface?Obj1?{
??a:?T
}
通過 extends 可以約束泛型:
//?extends?后可以接類型表達(dá)式
type?Fn2?=?extends?string?|?number>(...args:?any[])?=>?any;
//?泛型可以和函數(shù)泛型結(jié)合
type?Fn3?=?extends?string?|?number>(...args:?T[])?=>?I;
在 <> 中定義的泛型變量可以視為一個局部函數(shù)變量,例如上例中的 T,可以作為類型表達(dá)式在后續(xù)所有涉及類型的地方使用。
3.2 基于泛型創(chuàng)建新類型 - 函數(shù)調(diào)用
通過 泛型名<類型表達(dá)式> 即可使用泛型生成新類型,如下:
type?Fn3?=?extends?string?|?number>(...args:?T[])?=>?I;
type?MyFn?=?Fn3<boolean>;
/**?可以看到?Fn3?中的類型已經(jīng)被替換成了?boolean,也就是我們指定的參數(shù)類型
type?MyFn?=?(...args:?T[])?=>?boolean
*/
//?使用新類型
const?myfn:?MyFn?=?(a:?any)?=>?true;
上例中,返回的 MyFn 是一個新類型,可以直接使用新類型進(jìn)行類型計算,或者進(jìn)行類型限定。
3.3 泛型遞歸調(diào)用 - 函數(shù)遞歸
泛型調(diào)用支持遞歸:
type?RecursiveGenerics?=?T?extends?string???T?:?RecursiveGenerics;
在上個例子中,我們定義了一個泛型 RecursiveGenerics,當(dāng) T 是 string 的時候,RecursiveGenerics 返回 T,否則返回一個遞歸的結(jié)果!
例如遞歸我們就可以做很多有意思的事情了,比如類型對象的深度優(yōu)先遍歷、實現(xiàn)循環(huán)等等。下面我們給斐波那契數(shù)列計算的例子:
//?輔助函數(shù),暫時不用關(guān)心
type?NumberToArrayextends?any[]?=?[]>?=?T?extends?T???I['length']?extends?T???I?:?NumberToArrayany,?...I]>?:?never;
type?Add?=?[...NumberToArray,?...NumberToArray]['length']
type?Sub1extends?number>?=?NumberToArray?extends?[infer?_,?...infer?R]???R['length']?:?never;
type?Sub2extends?number>?=?NumberToArray?extends?[infer?_,?infer?__,?...infer?R]???R['length']?:?never;
//?計算斐波那契數(shù)列
type?Fibonacciextends?number>?=?
??T?extends?1???1?:
??T?extends?2???1?:
??Add>,?Fibonacci>>;
??
??type?Fibonacci9?=?Fibonacci<9>;
??/**?得到結(jié)果
??type?Fibonacci9?=?34
??*/
上述示例中我們成功使用類型完成了斐波那契數(shù)列的計算:

重點是下面幾句,根據(jù)條件類型判斷遞歸條件,然后調(diào)用遞歸。
下述示例使用的條件類型判斷邊界,下一小節(jié)會介紹條件類型
//?計算斐波那契數(shù)列
type?Fibonacciextends?number>?=?
??//?判斷邊界條件
??T?extends?1???1?:
??T?extends?2???1?:
??//?遞歸調(diào)用
??Add>,?Fibonacci>>;
??
??type?Fibonacci9?=?Fibonacci<9>;
??/**?得到結(jié)果
??type?Fibonacci9?=?34
4. 條件類型 - if else
4.1 條件類型
使用 extends 三元表達(dá)式能夠進(jìn)行條件的判斷,并返回一個新類型,語法如下:
類型表達(dá)式1?extends?類型表達(dá)式2???類型表達(dá)式?:?類型表達(dá)式
示例:
type?C?=?'a'?extends?'a'?|?'b'???true?:?false
/**
type?C?=?true
*/
這里有幾個注意點:
三元表達(dá)式的所有位置都可以使用類型表達(dá)式 返回值是一個類型表達(dá)式
4.2 Infer 推斷類型
可以使用 infer 關(guān)鍵字推斷條件類型中的某一個條件類型,然后將該類型賦值給一個臨時的類型變量。類型推斷可以用于 extends 后任何可以使用類型表達(dá)式的位置,示例:
type?Flatten?=?Type?extends?Array???Item?:?Type;
上述示例中,當(dāng) type 滿足 Array
type?T?=?Flatten<string[]>;?
/*?T?=?string,?因為推斷出?string[]?=?Array,所以?Item?=?string,類型返回?Item?*/
注意:infer 只能在條件類型里面使用。
通過 infer 關(guān)鍵字,可以實現(xiàn)很多的內(nèi)置類型的操作,比如 Parameters、ReturnType 等,實現(xiàn)方式如下:
//?自動推斷參數(shù)?P?的類型,如果是則泛型返回值是?P
type?MyParameters?=?T?extends?(...args:?infer?P)?=>?any???P?:?never;
/**
推斷參數(shù)的類型成功
type?Params?=?[a:?string,?b:?number]
*/
type?Params?=?MyParameters<(a:?string,?b:?number)?=>?void>;
//?同樣的方式,我們可以推斷?ReturnType
type?MyReturnType?=?T?extends?(...args:?any[])?=>?infer?R???R?:?never;
/**
團隊返回值的類型成功
type?Ret?=?void
*/
type?Ret?=?MyReturnType<(a:?string,?b:?number)?=>?void>;
infer 的能力很強大,可以推斷任何類型表達(dá)式,例如 infer 還可以和元組或者模版字符串結(jié)合,兩個示例如下:
//?計算元組中的第一個元素
type?Headextends?any[]>?=?T?extends?[infer?F,?...infer?R]???F?:?never;
//?解析?`1?+?2?+?3`?形式的字符串,并返回?AST
type?Parseextends?string>?=?T?extends?`${infer?ExpressionA}?+?${infer?ExpressionB}`???{
??type:?'operator',
??left:?Parse,
??right:?Parse
}:?{
??type:?'expression',
??value:?T
};
上述示例中,Head 的計算使用了上文提到的元組展開與合并知識點,然后結(jié)合本小節(jié)的infer,就可以推斷出數(shù)組的第一個元素。Parse 中使用了條件類型、遞歸知識點,再結(jié)合本小節(jié)的infer,就可以實現(xiàn)一個簡單的加法表達(dá)式解析器。主要實現(xiàn)是:T extends ${infer ExpressionA} + ${infer ExpressionB},如果字符串滿足 A + B 的模式,即可通過 infer 推斷出 A 和 B 的字符串。
4.3 條件聯(lián)合類型
如果條件類型的參數(shù)是一個聯(lián)合類型,則條件類型的計算結(jié)果相當(dāng)于,如下:
//?這里等價于?(string?exetends?any???string[]?:?never)?|?(number?exetends?any???number[]?:?never)
type?ToArray?=?Type?extends?any???Type[]?:?never;?
//?計算結(jié)果是?string[]?|?number[]
type?StrArrOrNumArr?=?ToArray<string?|?number>;?
利用這個特性我們可以實現(xiàn)一些有意思的功能,比如 Excludes:
type?Exclude?=?T?extends?I???never?:?T;
type?T0?=?Exclude<"a"?|?"b"?|?"c",?"a">;
/**
type?T0?=?"b"?|?"c"
*/
原理是聯(lián)合類型的每一個類型都會計算一次 extends,然后將最終的結(jié)果做聯(lián)合,never 在聯(lián)合過程中會去除。
二、類型表達(dá)式
類型表達(dá)式 僅是是本文給出的概念,便于讀者進(jìn)一步理解類型編程。目前官網(wǎng)文檔中沒有體現(xiàn)類似的概念,如果有不正確的地方,歡迎讀者指正。筆者認(rèn)為類型表達(dá)式是本文中最核心的一個概念,理解了此概念后,類型計算的問題都將迎刃而解。
值是一個類型的表達(dá)式就是類型表達(dá)式,通常:
定義的類型變量是一個類型表達(dá)式 類型操作符的操作結(jié)果是一個類型表達(dá)式,比如 A | B是一個類型表達(dá)式,會返回一個新的類型泛型調(diào)用結(jié)果是一個類型表達(dá)式,比如 A是一個類型表達(dá)式條件類型的結(jié)果是一個類型表達(dá)式,比如 A extend string ? true : false是一個類型表達(dá)式,返回值是類型 true 或者 false...
在需要使用類型的地方,我們就可以使用類型表達(dá)式:
類型變量定義:比如 type A = B,B 就可以是一個類型表達(dá)式,比如type A = string | Record泛型調(diào)用:比如 A,B 就可以是一個類型表示,比如A條件類型:比如 A extend B ? C : D,A、B、C、D 均可以是類型表達(dá)式...
總而言之,所有使用類型的地方,都可以使用類型表達(dá)式,比如類型變量賦值、條件類型、函數(shù)參數(shù)/返回值類型 等等位置。利用 TS 類型表達(dá)式的概念,我們就可以進(jìn)行強大的類型編程能力。
下面通過幾個示例來幫助理解類型表達(dá)式的概念。
首先可以拿上面的斐波那契數(shù)列計算作為第一個示例:
type?Fibonacci9?=?Fibonacci<9>;
//?Fibonacci<9>?是一個類型表達(dá)式,那么可以將其作為?Fibonacci?的輸入,如下:
type?Fibonacci99?=?Fibonacci9>>;?//?等價于?type?Fibonacci99?=?Fibonacci
Fibonacci<9> 是一個類型表達(dá)式,我們可以將這個類型表達(dá)式作為泛型的輸入,所以 Fibonacci 也是合法的!由此我們可以拓展,所有合法的類型表達(dá)式都可以在這里使用。
另一個示例是條件類型,我們前面介紹了條件類型的語法是:類型表達(dá)式1 extends 類型表達(dá)式2 ? 類型表達(dá)式3 : 類型表達(dá)式。
type?MyType?=?Fibonacci<9>?extends?Fibonacci<9>???Fibonacci<10>?:?Fibonacci<8>;
示例中的四個位置都可以使用類型表達(dá)式。
基于類型表達(dá)式的概念,我們可以通過堆砌小的類型表達(dá)式,完成復(fù)雜的類型編程操作!
三、常用知識點總結(jié)
1. 函數(shù)參數(shù)類型自動推導(dǎo)
通過泛型 + 函數(shù)參數(shù),可以定義一個類型變量,并且由函數(shù)參數(shù)自動推導(dǎo)類型變量的值:
function?identity<Type>(arg:?Type):?Type?{
??return?arg;
}
通過傳入一個 string 類型的參數(shù),可以推導(dǎo)出 Type=string ,同時這個類型參數(shù)可以在用于組合其他類型!

這個特性非常有用,有時候我們需要推斷出函數(shù)參數(shù)的類型,并將其保存到一個臨時類型變量中時,這個特性就可以很方便的實現(xiàn),下面實戰(zhàn)的鏈?zhǔn)秸{(diào)用中用到了這個特性。
2. 動態(tài)擴展類型變量
如下,T 中可保存上一次調(diào)用 option 后的值,然后通過類型遞歸,擴展 T 的類型,當(dāng)最后調(diào)用 get() 時,拿到的就是擴展后的 T 的類型:
type?Chainable?=?{
??optionextends?string,?V?extends?any>(key:?K,?value:?V):?Chainablein?K]:?V?}>
??get():?T
}
上述示例中,我們使用了默認(rèn)泛型 + 遞歸兩個特性,利用遞歸保存上下文,我們就可以實現(xiàn)對已有類型變量的擴展。利用這個特性我們可以保存鏈?zhǔn)秸{(diào)用中的上下文。
3. 動態(tài)更改對象類型的 key
通過 key in keyof T as xxx形式可以重寫 key。可以通過這種形式來實現(xiàn)動態(tài)更改對象類型的 key,比如實現(xiàn) OptionalKeys 或者 RequiresKeys 或者 ReadonlyKeys。
type?IsOptionalextends?keyof?T>?=?Partial>?extends?Pick???true?:?false;
type?OptionalKeys?=?keyof?{
??[K?in?keyof?T?as?IsOptional?extends?true???K?:?never]:?T[K];
};
type?RequiredKeys?=?{
??[K?in?keyof?T]:?IsOptional?extends?true???never?:?T[K]
}
這里注意 as 后面可以接一個類型表達(dá)式,我們可以通過臨時變量 K 以及輔助的類型表達(dá)式,實現(xiàn)對鍵的復(fù)雜的操作,比如增加、刪除特定的鍵,將特定的鍵標(biāo)記為可選,將特定的鍵標(biāo)記為 readonly 等等。
上述根據(jù)條件將 K 的類型重寫為 never 可以去掉該 key,但是注意將值的返回類型設(shè)置成 never 是無法更改 key 的數(shù)量的,如下:
type?RequiredKeys?=?{
??[K?in?keyof?T]:?IsOptional?extends?true???never?:?T[K]
}
返回的 never 值將會變?yōu)?undefined。
四、類型編程實戰(zhàn)
4.1 常用的 ts 內(nèi)置類型
參考:https://www.typescriptlang.org/docs/handbook/utility-types.html#excludetype-excludedunion

常用的有:
Partial
Omit Record Pick ReturnType Parameters
我們上面的示例中自己實現(xiàn)了 ReturnType、Parameters,其他的內(nèi)置類型的實現(xiàn)也類似。基于上述的基礎(chǔ)知識,我們都可以自行實現(xiàn)。
4.2 將某一個對象中的部分參數(shù)標(biāo)記為可選
使用 Partial 只能將所有參數(shù)標(biāo)記為可選,如何只標(biāo)記一部分參數(shù)呢?可以如下實現(xiàn):
type?Include?=?T?extends?I???T?:?never;
type?MyPartial?=?{
??[for?K?in?Exclude]:?T[K]?
}?&?{
??[for?K?in?Include]?:?T[K]?
}
上述示例將 T 的鍵分成兩部分,如果屬于 I 則標(biāo)記成可選,如果不是則為必須的。
4.3 給鏈?zhǔn)秸{(diào)用添加類型
題目:https://github.com/type-challenges/type-challenges/blob/master/questions/12-medium-chainable-options/README.md
type?Chainable?=?{
??optionextends?string,?V?extends?any>(key:?K,?value:?V):?Chainablein?K]:?V?}>
??get():?T
}
利用了 TS 函數(shù)的范性自動推斷能力以及遞歸函數(shù)存儲能力。
4.4 柯里化函數(shù)類型
type?Headextends?any[]>?=?T?extends?[infer?F,?...infer?R]???F?:?never;
type?Restextends?any[]>?=?T?extends?[infer?F,?...infer?R]???R?:?never;
declare?function?Currying<T?extends?any[],?P?extends?boolean>(fn:?(...args:?T)?=>?P):?CurryingRet<T,?P>;
type?CurryingRetextends?any[],?P>?=?T['length']?extends?0???P?:?(arg0:?Head )?=>?CurryingRet,?P>?;
這里實現(xiàn)的是簡化版本,更詳細(xì)的實現(xiàn)可以參考文章:https://medium.com/free-code-camp/typescript-curry-ramda-types-f747e99744ab。
Head 和 Rest 的計算上文有詳細(xì)介紹,這里我們主要利用了遞歸 + 函數(shù)泛型自動推斷的特性。
4.5 簡易加法表達(dá)式求值器
實現(xiàn):Calculator<'1 + 2 + 3'> 輸出 6。
先上效果:

實現(xiàn)思路:
實現(xiàn) Parse,將計算表達(dá)式解析成 AST 實現(xiàn) AST 遍歷器 實現(xiàn)求值器,輸出最終結(jié)果
/*?_____________?Your?Code?Here?_____________?*/
type?ASTExpressionNode?=?{
??type:?'operator'?|?'expression';
??left?:?ASTExpressionNode;
??right?:?ASTExpressionNode;
??value?:?keyof?NumberMap;
}
type?Parse?=?T?extends?`${infer?ExpressionA}?+?${infer?ExpressionB}`???{
??type:?'operator',
??left:?Parse,
??right:?Parse
}:?{
??type:?'expression',
??value:?T?extends?keyof?NumberMap???T?:?never
};
type?NumberToArrayextends?any[]?=?[]>?=?I['length']?extends?T???I?:?NumberToArrayany,?...I]>;
type?Add?=?[...NumberToArray,?...NumberToArray]['length'];
type?GetValueextends?ASTExpressionNode>?=?T['value']?extends?string???T['value']?:?never;
type?GetLeftextends?ASTExpressionNode>?=?T['left']?extends?ASTExpressionNode???T['left']?:?never;
type?GetRightextends?ASTExpressionNode>?=?T['right']?extends?ASTExpressionNode???T['right']?:?never;
type?NumberMap?=?{
??'0':?0,
??'1':?1,
??'2':?2,
??'3':?3,
??'4':?4,
};
type?Evaluateextends?ASTExpressionNode>?=?T['type']?extends?'expression'???NumberMap[`${GetValue} `]?:?Add>,?Evaluate>>;
type?Calculatorextends?string>?=?Evaluate>;
type?test1?=?Parse<'1?+?2'>;
/**?返回
type?test1?=?{
????type:?'operator';
????left:?{
????????type:?'expression';
????????value:?"1";
????};
????right:?{
????????type:?'expression';
????????value:?"2";
????};
}
*/
type?test2?=?Calculator<'1?+?2?+?3'>
/**?返回
type?test2?=?6
*/
這里我們利用了上面提到的幾乎所有知識點:
遞歸:表達(dá)式的解析和求值都使用了遞歸的能力 條件類型:實現(xiàn)遞歸的邊界判斷 類型推斷:通過類型推斷實現(xiàn) Head、Tail 等能力 ...
感興趣的同學(xué)還可以自行實現(xiàn)減法、乘法、除法、取模等操作~
五、常用 TS 類型工具庫
5.1 ts-toolbelt
封裝常用的 TS 類型操作。
地址: https://github.com/millsp/ts-toolbelt。
3.2 typetype
用于自動生成 TS 的類型。
地址: https://github.com/mistlog/typetype。
