<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          TypeScript 類(lèi)型編程: 從基礎(chǔ)到編譯器實(shí)戰(zhàn)

          共 11874字,需瀏覽 24分鐘

           ·

          2022-01-11 00:34

          作者簡(jiǎn)介: 吳明亮,抖音前端團(tuán)隊(duì) Monorepo 工程化框架核心開(kāi)發(fā)者。

          Typescript 的類(lèi)型編程可以理解為一門(mén)有限的函數(shù)式編程語(yǔ)言。

          本文假定讀者已經(jīng)使用過(guò) typescript 并且了解基礎(chǔ)的類(lèi)型概念,不會(huì)介紹基礎(chǔ)概念,主要專(zhuān)注于介紹如何進(jìn)行系統(tǒng)化的類(lèi)型編程。示例主要來(lái)源于官網(wǎng)、類(lèi)型挑戰(zhàn)倉(cāng)庫(kù)以及日常開(kāi)發(fā)。

          一、類(lèi)型編程基礎(chǔ)

          既然稱(chēng)作類(lèi)型編程,那自然和普通編程語(yǔ)言一樣,用于類(lèi)型變量定義語(yǔ)句、類(lèi)型表達(dá)式、類(lèi)型函數(shù)等等,本小結(jié)將詳細(xì)講述類(lèi)型編程的一些基礎(chǔ)知識(shí)。

          希望通過(guò)本文能夠幫助讀者更好的理解 TS 的類(lèi)型,讓日常開(kāi)發(fā)中的類(lèi)型操作更加容易。

          看大佬們用 ts 類(lèi)型實(shí)現(xiàn)編譯器,看起來(lái)非常其實(shí)(確實(shí)厲害=_=),不過(guò)理解這篇文章的思想后,讀者們也可以實(shí)現(xiàn)~文章最后一個(gè)示例實(shí)現(xiàn)了一個(gè) 簡(jiǎn)易加法表達(dá)式求值器。

          1. 類(lèi)型變量定義

          TS 定義類(lèi)型的方式有多種:

          • 使用 type。
          • 使用 interface。
          • 使用 class、enum 等等,其中 class、enum 可以既為值,又為類(lèi)型。

          TS 提供了大量的基礎(chǔ)類(lèi)型,可以直接在定義類(lèi)型變量時(shí)使用(關(guān)于基礎(chǔ)類(lèi)型的詳細(xì)介紹可以查閱 TS 文檔):

          • 基礎(chǔ)數(shù)據(jù)類(lèi)型,比如 string、number、boolean、symbol、undefined 等等。
          • 字面量類(lèi)型,比如 '123',5 等
          • 對(duì)象類(lèi)型,比如 { a: string }
          • 函數(shù)類(lèi)型,比如 (a: string) => void
          • 元組類(lèi)型,比如 [1, 2, 3]
          • 數(shù)組類(lèi)型,比如 string[]
          • ...
          //?使用?type?定義類(lèi)型變量,類(lèi)型是一個(gè)字面亮類(lèi)型?'123'
          type?TypeA?=?'123'

          //?使用?interface?定義類(lèi)型變量
          interface?TypeB?{
          ??a:?string
          }

          //?將對(duì)象類(lèi)型
          //?{
          //??b:?number
          //??c:?TypeA
          //?}?
          //?賦值給?TypeC
          //?TypeA?是上面定義的類(lèi)型變量,可以直接使用
          type?TypeC?=?{
          ??b:?number
          ??c:?TypeA
          }

          //?類(lèi)型變量可以直接賦值給另一個(gè)類(lèi)型變量
          type?D?=?TypeB
          //?將函數(shù)類(lèi)型賦值給?E
          type?E?=?(a:?string)?=>?void;

          基于 TS 的基礎(chǔ)類(lèi)型以及 type 等關(guān)鍵字,就可以定義自定義的類(lèi)型變量。

          2. 類(lèi)型操作符

          ts 中也定義了大量類(lèi)型操作,例如 &(對(duì)象類(lèi)型合并)、|(聯(lián)合類(lèi)型)等等,這些操作可以操作 TS 的類(lèi)型。

          2.1 & - 合并類(lèi)型對(duì)象

          & 合并多個(gè)類(lèi)型對(duì)象的鍵到一個(gè)類(lèi)型對(duì)象中。

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

          注意使用 & 時(shí),兩個(gè)類(lèi)型的鍵如果相同,但類(lèi)型不同,會(huì)報(bào)錯(cuò):

          type?A?=?{?a:?number?}
          type?B?=?{?a:?string?}
          type?C?=?A?&?B;
          /**
          報(bào)錯(cuò):
          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)合類(lèi)型

          |將多個(gè)類(lèi)型組成聯(lián)合類(lèi)型:

          type?A?=?string?|?number;
          type?B?=?string;

          此時(shí)類(lèi)型 A 既可以是 string 又可以是 number,類(lèi)型 B 是類(lèi)型 A 的子集,所有能賦值給類(lèi)型 B 的值都可以賦值給類(lèi)型 A。

          2.3 keyof - 獲取對(duì)象類(lèi)型的鍵

          keyof 可以獲取某些對(duì)象類(lèi)型的鍵:

          interface?People?{
          ??a:?string;
          ??b:?string;
          }

          //?返回?'a'?|?'b'
          type?KeyofPeople?=?keyof?People;
          //?type?KeyofPeople?=?'a'?|?'b';

          用這種方式可以獲取某個(gè)類(lèi)型的所有鍵。

          注意 keyof 只能對(duì)類(lèi)型使用,如果想要對(duì)值使用,需要先使用 typeof 獲取類(lèi)型。

          2.4 typeof - 獲取值的類(lèi)型

          typeof 可以獲取值的類(lèi)型。

          //?獲取對(duì)象的類(lèi)型
          const?obj?=?{?a:?'123',?b:?123?}
          type?Obj?=?typeof?obj;
          /**
          type?Obj?=?{
          ????a:?string;
          ????b:?number;
          }
          */


          //?獲取函數(shù)的類(lèi)型
          function?fn(a:?Obj,?b:?number)?{
          ??return?true;
          }
          type?Fn?=?typeof?fn;
          /**
          type?Fn?=?(a:?Obj,?b:?number)?=>?boolean
          */


          //?...獲取各種值的類(lèi)型

          注意對(duì)于 enum 需要先進(jìn)行 typeof 操作獲取類(lèi)型,才能通過(guò) keyof 等類(lèi)型操作完成正確的類(lèi)型計(jì)算(因?yàn)?enum 可以是類(lèi)型也可以是值,如果不使用 typeof 會(huì)當(dāng)值計(jì)算):

          enum?E1?{
          ??A,
          ??B,
          ??C
          }

          type?TE1?=?keyof?E1;
          /**
          拿到的是錯(cuò)誤的類(lèi)型
          type?TE1?=?"toString"?|?"toFixed"?|?"toExponential"?|?"toPrecision"?|?"valueOf"?|?"toLocaleString"
          */


          type?TE2?=?keyof?typeof?E1;
          /**
          拿到的是正確的類(lèi)型
          type?TE2?=?"A"?|?"B"?|?"C"
          */

          2.5 [...] - 元組展開(kāi)與合并

          元組可以視為長(zhǎng)度確定的數(shù)組,元組中的每一項(xiàng)可以是任意類(lèi)型。通過(guò) [...元組, ...元組] 語(yǔ)法可以合并兩個(gè)元組。

          結(jié)合元組展開(kāi)以及 infer 類(lèi)型推斷,可以實(shí)現(xiàn)類(lèi)型中的數(shù)組操作,比如 pop(),后文介紹 infer 時(shí)將詳細(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] - 遍歷對(duì)象鍵值

          在對(duì)象類(lèi)型中,可以通過(guò) [臨時(shí)類(lèi)型變量 in 聯(lián)合類(lèi)型] 語(yǔ)法來(lái)遍歷對(duì)象的鍵,示例如下:

          //?下述示例遍歷?'1'?|?'2'?|?3'?三個(gè)值,然后依次賦值給?K,K?作為一個(gè)臨時(shí)的類(lèi)型變量可以在后面直接使用?
          /**
          下述示例最終的計(jì)算結(jié)果是:
          type?MyType?=?{
          ????1:?"1";
          ????2:?"2";
          ????3:?"3";
          }
          因?yàn)?K?類(lèi)型變量的值在每次遍歷中依次是?'1',?'2',?'3'?所以每次遍歷時(shí)對(duì)象的鍵和值分別是?{?'1':?'2'?}?{?'2':?'2'?}?和?{?'3':?'3'?},
          最終結(jié)果是這個(gè)三個(gè)結(jié)果取?&
          */

          type?MyType?=?{
          ??//?注意能遍歷的類(lèi)型只有?string、number、symbol,也就是對(duì)象鍵允許的類(lèi)型
          ??[K?in?'1'?|?'2'?|?'3']:?K?
          }

          [in] 常常和 keyof 搭配使用,遍歷某一個(gè)對(duì)象的鍵,做相應(yīng)的計(jì)算后得到新的類(lèi)型,如下:

          type?Obj?=?{
          ??a:?string;
          ??b:?number;
          }
          /**
          遍歷?Obj?的所有鍵,然后將所有鍵對(duì)應(yīng)的值的類(lèi)型改成?boolean?|?K,返回結(jié)果如下:
          type?MyObj?=?{
          ????a:?boolean?|?"a";
          ????b:?boolean?|?"b";
          }
          這樣我們就實(shí)現(xiàn)了給?Obj?的所有值的類(lèi)型加上?|?boolean?的效果
          */

          type?MyObj?=?{
          ??[K?in?keyof?Obj]:?boolean?|?K
          }

          in 后面還可以接 as,as 后面可以接類(lèi)型表達(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. 泛型 - 類(lèi)型函數(shù)

          TS 的泛型可以類(lèi)比 Javascript 中的函數(shù)

          3.1 定義泛型

          使用定義泛型:

          //?接口泛型
          interface?Obj1?{
          ??a:?T
          }
          //?使用?type?也能定義泛型
          type?Type1?=?{?b:?T?}
          //?函數(shù)泛型
          type?Fn1?=?(...args:?any[])?=>?any;

          //?泛型也可以有默認(rèn)值,這樣如果沒(méi)有指定泛型參數(shù),默認(rèn)是?string
          interface?Obj1?{
          ??a:?T
          }

          通過(guò) extends 可以約束泛型:

          //?extends?后可以接類(lèi)型表達(dá)式
          type?Fn2?=?extends?string?|?number>(...args:?any[])?=>?any;

          //?泛型可以和函數(shù)泛型結(jié)合
          type?Fn3?=?extends?string?|?number>(...args:?T[])?=>?I;

          <> 中定義的泛型變量可以視為一個(gè)局部函數(shù)變量,例如上例中的 T,可以作為類(lèi)型表達(dá)式在后續(xù)所有涉及類(lèi)型的地方使用。

          3.2 基于泛型創(chuàng)建新類(lèi)型 - 函數(shù)調(diào)用

          通過(guò) 泛型名<類(lèi)型表達(dá)式> 即可使用泛型生成新類(lèi)型,如下:

          type?Fn3?=?extends?string?|?number>(...args:?T[])?=>?I;

          type?MyFn?=?Fn3<boolean>;
          /**?可以看到?Fn3?中的類(lèi)型已經(jīng)被替換成了?boolean,也就是我們指定的參數(shù)類(lèi)型
          type?MyFn?=?(...args:?T[])?=>?boolean
          */


          //?使用新類(lèi)型
          const?myfn:?MyFn?=?(a:?any)?=>?true;

          上例中,返回的 MyFn 是一個(gè)新類(lèi)型,可以直接使用新類(lèi)型進(jìn)行類(lèi)型計(jì)算,或者進(jìn)行類(lèi)型限定。

          3.3 泛型遞歸調(diào)用 - 函數(shù)遞歸

          泛型調(diào)用支持遞歸:

          type?RecursiveGenerics?=?T?extends?string???T?:?RecursiveGenerics;

          在上個(gè)例子中,我們定義了一個(gè)泛型 RecursiveGenerics,當(dāng) T 是 string 的時(shí)候,RecursiveGenerics 返回 T,否則返回一個(gè)遞歸的結(jié)果!

          例如遞歸我們就可以做很多有意思的事情了,比如類(lèi)型對(duì)象的深度優(yōu)先遍歷、實(shí)現(xiàn)循環(huán)等等。下面我們給斐波那契數(shù)列計(jì)算的例子:

          //?輔助函數(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;

          //?計(jì)算斐波那契數(shù)列
          type?Fibonacciextends?number>?=?
          ??T?extends?1???1?:
          ??T?extends?2???1?:
          ??Add>,?Fibonacci>>;
          ??
          ??type?Fibonacci9?=?Fibonacci<9>;
          ??/**?得到結(jié)果
          ??type?Fibonacci9?=?34
          ??*/

          上述示例中我們成功使用類(lèi)型完成了斐波那契數(shù)列的計(jì)算:

          重點(diǎn)是下面幾句,根據(jù)條件類(lèi)型判斷遞歸條件,然后調(diào)用遞歸。

          下述示例使用的條件類(lèi)型判斷邊界,下一小節(jié)會(huì)介紹條件類(lèi)型

          //?計(jì)算斐波那契數(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. 條件類(lèi)型 - if else

          4.1 條件類(lèi)型

          使用 extends 三元表達(dá)式能夠進(jìn)行條件的判斷,并返回一個(gè)新類(lèi)型,語(yǔ)法如下:

          類(lèi)型表達(dá)式1?extends?類(lèi)型表達(dá)式2???類(lèi)型表達(dá)式?:?類(lèi)型表達(dá)式

          示例:

          type?C?=?'a'?extends?'a'?|?'b'???true?:?false
          /**
          type?C?=?true
          */

          這里有幾個(gè)注意點(diǎn):

          4.2 Infer 推斷類(lèi)型

          可以使用 infer 關(guān)鍵字推斷條件類(lèi)型中的某一個(gè)條件類(lèi)型,然后將該類(lèi)型賦值給一個(gè)臨時(shí)的類(lèi)型變量。類(lèi)型推斷可以用于 extends 后任何可以使用類(lèi)型表達(dá)式的位置,示例:

          type?Flatten?=?Type?extends?Array???Item?:?Type;

          上述示例中,當(dāng) type 滿(mǎn)足 Array模式時(shí),將會(huì)自動(dòng)推斷出 T 的類(lèi)型,并賦值給 Item。例如:

          type?T?=?Flatten<string[]>;?
          /*?T?=?string,?因?yàn)橥茢喑?string[]?=?Array,所以?Item?=?string,類(lèi)型返回?Item?*/

          注意:infer 只能在條件類(lèi)型里面使用。

          通過(guò) infer 關(guān)鍵字,可以實(shí)現(xiàn)很多的內(nèi)置類(lèi)型的操作,比如 Parameters、ReturnType 等,實(shí)現(xiàn)方式如下:

          //?自動(dòng)推斷參數(shù)?P?的類(lèi)型,如果是則泛型返回值是?P
          type?MyParameters?=?T?extends?(...args:?infer?P)?=>?any???P?:?never;
          /**
          推斷參數(shù)的類(lèi)型成功
          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;
          /**
          團(tuán)隊(duì)返回值的類(lèi)型成功
          type?Ret?=?void
          */

          type?Ret?=?MyReturnType<(a:?string,?b:?number)?=>?void>;

          infer 的能力很強(qiáng)大,可以推斷任何類(lèi)型表達(dá)式,例如 infer 還可以和元組或者模版字符串結(jié)合,兩個(gè)示例如下:

          //?計(jì)算元組中的第一個(gè)元素
          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 的計(jì)算使用了上文提到的元組展開(kāi)與合并知識(shí)點(diǎn),然后結(jié)合本小節(jié)的infer,就可以推斷出數(shù)組的第一個(gè)元素。Parse 中使用了條件類(lèi)型遞歸知識(shí)點(diǎn),再結(jié)合本小節(jié)的infer,就可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的加法表達(dá)式解析器。主要實(shí)現(xiàn)是:T extends ${infer ExpressionA} + ${infer ExpressionB},如果字符串滿(mǎn)足 A + B 的模式,即可通過(guò) infer 推斷出 A 和 B 的字符串。

          4.3 條件聯(lián)合類(lèi)型

          如果條件類(lèi)型的參數(shù)是一個(gè)聯(lián)合類(lèi)型,則條件類(lèi)型的計(jì)算結(jié)果相當(dāng)于,如下:

          //?這里等價(jià)于?(string?exetends?any???string[]?:?never)?|?(number?exetends?any???number[]?:?never)
          type?ToArray?=?Type?extends?any???Type[]?:?never;?
          //?計(jì)算結(jié)果是?string[]?|?number[]
          type?StrArrOrNumArr?=?ToArray<string?|?number>;?

          利用這個(gè)特性我們可以實(shí)現(xiàn)一些有意思的功能,比如 Excludes

          type?Exclude?=?T?extends?I???never?:?T;
          type?T0?=?Exclude<"a"?|?"b"?|?"c",?"a">;
          /**
          type?T0?=?"b"?|?"c"
          */

          原理是聯(lián)合類(lèi)型的每一個(gè)類(lèi)型都會(huì)計(jì)算一次 extends,然后將最終的結(jié)果做聯(lián)合,never 在聯(lián)合過(guò)程中會(huì)去除。

          二、類(lèi)型表達(dá)式

          類(lèi)型表達(dá)式 僅是是本文給出的概念,便于讀者進(jìn)一步理解類(lèi)型編程。目前官網(wǎng)文檔中沒(méi)有體現(xiàn)類(lèi)似的概念,如果有不正確的地方,歡迎讀者指正。筆者認(rèn)為類(lèi)型表達(dá)式是本文中最核心的一個(gè)概念,理解了此概念后,類(lèi)型計(jì)算的問(wèn)題都將迎刃而解。

          值是一個(gè)類(lèi)型的表達(dá)式就是類(lèi)型表達(dá)式,通常:

          在需要使用類(lèi)型的地方,我們就可以使用類(lèi)型表達(dá)式:

          總而言之,所有使用類(lèi)型的地方,都可以使用類(lèi)型表達(dá)式,比如類(lèi)型變量賦值、條件類(lèi)型、函數(shù)參數(shù)/返回值類(lèi)型 等等位置。利用 TS 類(lèi)型表達(dá)式的概念,我們就可以進(jìn)行強(qiáng)大的類(lèi)型編程能力。

          下面通過(guò)幾個(gè)示例來(lái)幫助理解類(lèi)型表達(dá)式的概念。

          首先可以拿上面的斐波那契數(shù)列計(jì)算作為第一個(gè)示例:

          type?Fibonacci9?=?Fibonacci<9>;
          //?Fibonacci<9>?是一個(gè)類(lèi)型表達(dá)式,那么可以將其作為?Fibonacci?的輸入,如下:
          type?Fibonacci99?=?Fibonacci9>>;?//?等價(jià)于?type?Fibonacci99?=?Fibonacci

          Fibonacci<9> 是一個(gè)類(lèi)型表達(dá)式,我們可以將這個(gè)類(lèi)型表達(dá)式作為泛型的輸入,所以 Fibonacci> 也是合法的!由此我們可以拓展,所有合法的類(lèi)型表達(dá)式都可以在這里使用。

          另一個(gè)示例是條件類(lèi)型,我們前面介紹了條件類(lèi)型的語(yǔ)法是:類(lèi)型表達(dá)式1 extends 類(lèi)型表達(dá)式2 ? 類(lèi)型表達(dá)式3 : 類(lèi)型表達(dá)式

          type?MyType?=?Fibonacci<9>?extends?Fibonacci<9>???Fibonacci<10>?:?Fibonacci<8>;

          示例中的四個(gè)位置都可以使用類(lèi)型表達(dá)式。

          基于類(lèi)型表達(dá)式的概念,我們可以通過(guò)堆砌小的類(lèi)型表達(dá)式,完成復(fù)雜的類(lèi)型編程操作!

          三、常用知識(shí)點(diǎn)總結(jié)

          1. 函數(shù)參數(shù)類(lèi)型自動(dòng)推導(dǎo)

          通過(guò)泛型 + 函數(shù)參數(shù),可以定義一個(gè)類(lèi)型變量,并且由函數(shù)參數(shù)自動(dòng)推導(dǎo)類(lèi)型變量的值:

          function?identity<Type>(arg:?Type):?Type?{
          ??return?arg;
          }

          通過(guò)傳入一個(gè) string 類(lèi)型的參數(shù),可以推導(dǎo)出 Type=string ,同時(shí)這個(gè)類(lèi)型參數(shù)可以在用于組合其他類(lèi)型!

          這個(gè)特性非常有用,有時(shí)候我們需要推斷出函數(shù)參數(shù)的類(lèi)型,并將其保存到一個(gè)臨時(shí)類(lèi)型變量中時(shí),這個(gè)特性就可以很方便的實(shí)現(xiàn),下面實(shí)戰(zhàn)的鏈?zhǔn)秸{(diào)用中用到了這個(gè)特性。

          2. 動(dòng)態(tài)擴(kuò)展類(lèi)型變量

          如下,T 中可保存上一次調(diào)用 option 后的值,然后通過(guò)類(lèi)型遞歸,擴(kuò)展 T 的類(lèi)型,當(dāng)最后調(diào)用 get() 時(shí),拿到的就是擴(kuò)展后的 T 的類(lèi)型:

          type?Chainable?=?{
          ??optionextends?string,?V?extends?any>(key:?K,?value:?V):?Chainablein?K]:?V?}>
          ??get():?T
          }

          上述示例中,我們使用了默認(rèn)泛型 + 遞歸兩個(gè)特性,利用遞歸保存上下文,我們就可以實(shí)現(xiàn)對(duì)已有類(lèi)型變量的擴(kuò)展。利用這個(gè)特性我們可以保存鏈?zhǔn)秸{(diào)用中的上下文。

          3. 動(dòng)態(tài)更改對(duì)象類(lèi)型的 key

          通過(guò) key in keyof T as xxx形式可以重寫(xiě) key。可以通過(guò)這種形式來(lái)實(shí)現(xiàn)動(dòng)態(tài)更改對(duì)象類(lèi)型的 key,比如實(shí)現(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 后面可以接一個(gè)類(lèi)型表達(dá)式,我們可以通過(guò)臨時(shí)變量 K 以及輔助的類(lèi)型表達(dá)式,實(shí)現(xiàn)對(duì)鍵的復(fù)雜的操作,比如增加、刪除特定的鍵,將特定的鍵標(biāo)記為可選,將特定的鍵標(biāo)記為 readonly 等等。

          上述根據(jù)條件將 K 的類(lèi)型重寫(xiě)為 never 可以去掉該 key,但是注意將值的返回類(lèi)型設(shè)置成 never 是無(wú)法更改 key 的數(shù)量的,如下:

          type?RequiredKeys?=?{
          ??[K?in?keyof?T]:?IsOptional?extends?true???never?:?T[K]
          }

          返回的 never 值將會(huì)變?yōu)?undefined。

          四、類(lèi)型編程實(shí)戰(zhàn)

          4.1 常用的 ts 內(nèi)置類(lèi)型

          參考:https://www.typescriptlang.org/docs/handbook/utility-types.html#excludetype-excludedunion

          常用的有:

          我們上面的示例中自己實(shí)現(xiàn)了 ReturnType、Parameters,其他的內(nèi)置類(lèi)型的實(shí)現(xiàn)也類(lèi)似。基于上述的基礎(chǔ)知識(shí),我們都可以自行實(shí)現(xiàn)。

          4.2 將某一個(gè)對(duì)象中的部分參數(shù)標(biāo)記為可選

          使用 Partial 只能將所有參數(shù)標(biāo)記為可選,如何只標(biāo)記一部分參數(shù)呢?可以如下實(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)用添加類(lèi)型

          題目: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ù)的范性自動(dòng)推斷能力以及遞歸函數(shù)存儲(chǔ)能力。

          4.4 柯里化函數(shù)類(lèi)型

          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>?;

          這里實(shí)現(xiàn)的是簡(jiǎn)化版本,更詳細(xì)的實(shí)現(xiàn)可以參考文章:https://medium.com/free-code-camp/typescript-curry-ramda-types-f747e99744ab。

          Head 和 Rest 的計(jì)算上文有詳細(xì)介紹,這里我們主要利用了遞歸 + 函數(shù)泛型自動(dòng)推斷的特性。

          4.5 簡(jiǎn)易加法表達(dá)式求值器

          實(shí)現(xiàn):Calculator<'1 + 2 + 3'> 輸出 6。

          先上效果:

          實(shí)現(xiàn)思路:

          /*?_____________?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
          */

          這里我們利用了上面提到的幾乎所有知識(shí)點(diǎn):

          感興趣的同學(xué)還可以自行實(shí)現(xiàn)減法、乘法、除法、取模等操作~

          五、常用 TS 類(lèi)型工具庫(kù)

          5.1 ts-toolbelt

          封裝常用的 TS 類(lèi)型操作。

          地址: https://github.com/millsp/ts-toolbelt。

          3.2 typetype

          用于自動(dòng)生成 TS 的類(lèi)型。

          地址: https://github.com/mistlog/typetype。

          瀏覽 52
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    黄色三级网站 | 正在播放熟女 | 精品国产污污污免费入口15 | 超级A片在线观看 | 私人女仆扫地偷懒被主人颜色吃现在被喷尿洗脸 |