【萬(wàn)字長(zhǎng)文】深入理解 Typescript 高級(jí)用法

點(diǎn)擊上方“前端技術(shù)磚家”關(guān)注
?「前言」:這里的標(biāo)題看起來(lái)是 "高級(jí)用法",不少同學(xué)可能就表示被勸退了。其實(shí)
?Typescript作為一門強(qiáng)類型編程語(yǔ)言,最具特色的就是他的類型表達(dá)能力,這是很多完備的后端語(yǔ)言都難以媲美的 說(shuō)的很對(duì),但PHP是最好的語(yǔ)言,所以如果你搞懂了他的類型系統(tǒng),對(duì)將來(lái)的日常開發(fā)一定是大有裨益的,但過(guò)于靈活的類型系統(tǒng)也注定了Typescript無(wú)法成為一門純粹的靜態(tài)語(yǔ)言,不過(guò)每一行代碼都有代碼提示他不香嘛?
大綱
基礎(chǔ)準(zhǔn)備 Typescript 類型系統(tǒng)簡(jiǎn)述 Typescript 的類型是支持定義 "函數(shù)定義" 的 Typescript 的類型是支持 "條件判斷" 的 Typescript 的類型是支持 "數(shù)據(jù)結(jié)構(gòu)" 的 Typescript 的類型是支持 "作用域" 的 Typescript 的類型是支持 "遞歸" 的 小結(jié) "高級(jí)用法" 的使用場(chǎng)景與價(jià)值 哪些用法可以被稱為 "高級(jí)用法" 舉例說(shuō)明 "高級(jí)用法" 的使用場(chǎng)景 小結(jié) 類型推導(dǎo)與泛型操作符 流動(dòng)的類型(類型編寫思路) Typescript 代碼哲學(xué) 常見類型推導(dǎo)實(shí)現(xiàn)邏輯梳理 類型的傳遞(流動(dòng)) 類型的過(guò)濾與分流 小結(jié) 定制化擴(kuò)展你的 Typescript Typescript Service Plugins 的產(chǎn)生背景、功能定位、基礎(chǔ)使用 市面上已有的 Typescript Service Plugins 舉例介紹 參考資料鏈接 Q&A 可以利用 Typescript Service Plugin(例如配置 eslint 規(guī)則)阻塞編譯或者在編譯時(shí)告警嗎?
基礎(chǔ)準(zhǔn)備
閱讀本文需要具備的基礎(chǔ)知識(shí)。
預(yù)備知識(shí)
本文的定位為理解高級(jí)用法,故不會(huì)涉及過(guò)多基礎(chǔ)知識(shí)相關(guān)的講解,需要讀者自己去完善這方面的知識(shí)儲(chǔ)備。
此文檔的內(nèi)容默認(rèn)要求讀者已經(jīng)具備以下知識(shí):
有 Javascript或其他語(yǔ)言編程經(jīng)驗(yàn)。有 Typescript實(shí)際使用經(jīng)驗(yàn),最好在正經(jīng)項(xiàng)目中完整地使用過(guò)。了解 Typescript基礎(chǔ)語(yǔ)法以及常見關(guān)鍵字地作用。對(duì) Typescript的類型系統(tǒng)架構(gòu)有一個(gè)最基本的了解。
相關(guān)資源推薦
Typescript 官網(wǎng)[1] TypeScript Deep Dive[2] TypeScript GitHub地址[3]
背景
初用 Typescript 開發(fā)的同學(xué)一定有這樣的困擾:
代碼代碼提示并不智能,似乎只能顯式的定義類型,才能有代碼提示,無(wú)法理解這樣的編程語(yǔ)言居然有這么多人趨之若鶩。 各種各樣的類型報(bào)錯(cuò)苦不堪言,本以為聽信網(wǎng)上說(shuō) Typescript可以提高代碼可維護(hù)性,結(jié)果卻發(fā)現(xiàn)徒增了不少開發(fā)負(fù)擔(dān)。顯式地定義所有的類型似乎能應(yīng)付大部分常見,但遇到有些復(fù)雜的情況卻發(fā)現(xiàn)無(wú)能為力,只能含恨寫下若干的 as any默默等待代碼review時(shí)的公開處刑。項(xiàng)目急時(shí)間緊卻發(fā)現(xiàn) Typescript成了首要難題,思索片刻決定投靠的Anyscript,快速開發(fā)業(yè)務(wù)邏輯,待到春暖花開時(shí)再回來(lái)補(bǔ)充類型。雙倍的工作量,雙倍的快樂(lè)只有自己才懂。
為了避免以上悲劇的發(fā)生或者重演,我們只有在對(duì)它有更加深刻的理解之后,才能在開發(fā)時(shí)游刃有余、在擼碼時(shí)縱橫捭闔。
Typescript 類型系統(tǒng)簡(jiǎn)述
?「思考題」:有人說(shuō)
?Typescript=Type?+Javascript,那么拋開Javascript不談,這里的Type是一門完備的編程語(yǔ)言嗎?
Typescript 的類型是支持定義 "函數(shù)定義" 的
有過(guò)編程經(jīng)驗(yàn)的同學(xué)都知道,函數(shù)是一門編程語(yǔ)言中最基礎(chǔ)的功能之一,函數(shù)是過(guò)程化、面向?qū)ο蟆⒑瘮?shù)式編程中程序封裝的基本單元,其重要程度不言而喻。
函數(shù)可以幫助我們做很多事,比如 :
函數(shù)可以把程序封裝成一個(gè)個(gè)功能,并形成函數(shù)內(nèi)部的變量作用域,通過(guò)靜態(tài)變量保存函數(shù)狀態(tài),通過(guò)返回值返回結(jié)果。 函數(shù)可以幫助我們實(shí)現(xiàn)過(guò)程的復(fù)用,如果一段邏輯可以被使用多次,就封裝成函數(shù),被其它過(guò)程多次調(diào)用。 函數(shù)也可以幫我們更好地組織代碼結(jié)構(gòu),幫助我們更好地維護(hù)代碼。
那么言歸正傳,如何在 Typescript 類型系統(tǒng)中定義函數(shù)呢?
Typescript 中類型系統(tǒng)中的的函數(shù)被稱作 ?泛型操作符,其定義的簡(jiǎn)單的方式就是使用 type 關(guān)鍵字:
//?這里我們就定義了一個(gè)最簡(jiǎn)單的泛型操作符
type?foo?=?T;
這里的代碼如何理解呢,其實(shí)這里我把代碼轉(zhuǎn)換成大家最熟悉的 Javascript 代碼其實(shí)就不難理解了:
//?把上面的類型代碼轉(zhuǎn)換成?`JavaScript`?代碼
function?foo(T)?{
??return?T
}
那么看到這里有同學(xué)心里要犯嘀咕了,心想你這不是忽悠我嘛?這不就是 ?Typescript 中定義類型的方式嘛?這玩意兒我可太熟了,這玩意兒不就和 interface 一樣的嘛,我還知道 Type 關(guān)鍵字和 interface 關(guān)鍵字有啥細(xì)微的區(qū)別呢!
嗯,同學(xué)你說(shuō)的太對(duì)了,不過(guò)你不要著急,接著聽我說(shuō),其實(shí)類型系統(tǒng)中的函數(shù)還支持對(duì)入?yún)⒌募s束。
//?這里我們就對(duì)入?yún)?T?進(jìn)行了類型約束
type?fooextends?string>?=?T;
那么把這里的代碼轉(zhuǎn)換成我們常見的 Typescript ?是什么樣子的呢?
function?foo(T:?string)?{
??return?T
}
當(dāng)然啦我們也可以給它設(shè)置默認(rèn)值:
//?這里我們就對(duì)入?yún)?T?增加了默認(rèn)值
type?fooextends?string?=?'hello?world'>?=?T;
那么這里的代碼轉(zhuǎn)換成我們常見的 Typescript ?就是這樣的:
function?foo(T:?string?=?'hello?world')?{
??return?T
}
看到這里肯定有同學(xué)迫不及待地想要提問(wèn)了:「那能不能像 JS 里的函數(shù)一樣支持剩余參數(shù)呢?」
很遺憾,目前暫時(shí)是不支持的,但是在我們?nèi)粘i_發(fā)中一定是有這樣的需求存在的。那就真的沒(méi)有辦法了嘛?其實(shí)也不一定,我們可以通過(guò)一些騷操作來(lái)模擬這種場(chǎng)景,當(dāng)然這個(gè)是后話了,這里就不作拓展了。
Typescript 的類型是支持 "條件判斷" 的
?人生總會(huì)面臨很多選擇,編程也是一樣。
——我瞎編的
?
條件判斷也是編程語(yǔ)言中最基礎(chǔ)的功能之一,也是我們?nèi)粘]碼過(guò)程成最常用的功能,無(wú)論是 if else 還是 三元運(yùn)算符,相信大家都有使用過(guò)。
那么在 Typescript 類型系統(tǒng)中的類型判斷要怎么實(shí)現(xiàn)呢?
其實(shí)這在 Typescript 官方文檔被稱為 條件類型(Conditional Types),定義的方法也非常簡(jiǎn)單,就是使用 extends 關(guān)鍵字。
T?extends?U???X?:?Y;
這里相信聰明的你一眼就看出來(lái)了,這不就是 三元運(yùn)算符 嘛!是的,而且這和三元運(yùn)算符的也發(fā)也非常像,如果 T extends U 為 true 那么 返回 X ,否則返回 Y。
結(jié)合之前剛剛講過(guò)的 "函數(shù)",我們就可以簡(jiǎn)單的拓展一下:
type?num?=?1;
type?str?=?'hello?world';
type?IsNumber?=?N?extends?number???'yes,?is?a?number'?:?'no,?not?a?number';
type?result1?=?IsNumber;?//?"yes,?is?a?number"
type?result2?=?IsNumber;?//?"no,?not?a?number"
這里我們就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的帶判斷邏輯的函數(shù)。
Typescript 的類型是支持 "數(shù)據(jù)結(jié)構(gòu)" 的
模擬真實(shí)數(shù)組
看到這里肯定有同學(xué)就笑了,這還不簡(jiǎn)單,就舉例來(lái)說(shuō),Typescript 中最常見數(shù)據(jù)類型就是 數(shù)組(Array) 或者 元組(tuple)。
同學(xué)你說(shuō)的很對(duì),「那你知道如何對(duì) 元組類型 作 push、pop、shift、unshift 這些行為操作嗎?」
其實(shí)這些操作都是可以被實(shí)現(xiàn)的:
//?這里定義一個(gè)工具類型,簡(jiǎn)化代碼
type?ReplaceValByOwnKeyextends?any>?=?{?[P?in?keyof?T]:?S[P]?};
//?shift?action
type?ShiftActionextends?any[]>?=?((...args:?T)?=>?any)?extends?((arg1:?any,?...rest:?infer?R)?=>?any)???R?:?never;
//?unshift?action
type?UnshiftAction<T?extends?any[],?A>?=?((args1:?A,?...rest:?T)?=>?any)?extends?((...args:?infer?R)?=>?any)???R?:?never;
//?pop?action
type?PopAction<T?extends?any[]>?=?ReplaceValByOwnKey<ShiftAction<T>,?T>;
//?push?action
type?PushAction<T?extends?any[],?E>?=?ReplaceValByOwnKey<UnshiftAction<T,?any>,?T?&?{?[k:?string]:?E?}>;
//?test?...
type?tuple?=?['vue',?'react',?'angular'];
type?resultWithShiftAction?=?ShiftAction<tuple>;?//?["react",?"angular"]
type?resultWithUnshiftAction?=?UnshiftAction<tuple,?'jquery'>;?//?["jquery",?"vue",?"react",?"angular"]
type?resultWithPopAction?=?PopAction<tuple>;?//?["vue",?"react"]
type?resultWithPushAction?=?PushAction<tuple,?'jquery'>;?//?["vue",?"react",?"angular",?"jquery"]
?「注意」:這里的代碼僅用于測(cè)試,操作某些復(fù)雜類型可能會(huì)報(bào)錯(cuò),需要做進(jìn)一步兼容處理,這里簡(jiǎn)化了相關(guān)代碼,請(qǐng)勿用于生產(chǎn)環(huán)境!
?
相信讀到這里,大部分同學(xué)應(yīng)該可以已經(jīng)可以感受到 Typescript 類型系統(tǒng)的強(qiáng)大之處了,其實(shí)這里還是繼續(xù)完善,為元組增加 concat 、map 等數(shù)組的常用的功能,這里不作詳細(xì)探討,留給同學(xué)們自己課后嘗試吧。
但是其實(shí)上面提到的 "數(shù)據(jù)類型" 并不是我這里想講解的 "數(shù)據(jù)類型",上述的數(shù)據(jù)類型本質(zhì)上還是服務(wù)于代碼邏輯的數(shù)據(jù)類型,其實(shí)并不是服務(wù)于 類型系統(tǒng) 本身的數(shù)據(jù)類型。
上面這句話的怎么理解呢?
不管是 數(shù)組 還是 元組,在廣義的理解中,其實(shí)都是用來(lái)對(duì) 「數(shù)據(jù)」 作 「批量操作」,同理,服務(wù)于 類型系統(tǒng) 本身的數(shù)據(jù)結(jié)構(gòu),應(yīng)該也可以對(duì) 「類型」 作 「批量操作」。
那么如何對(duì) 「類型」 作 「批量操作」 呢?或者說(shuō)服務(wù)于 類型系統(tǒng) 中的 「數(shù)組」 是什么呢?
下面就引出了本小節(jié)真正的 "數(shù)組":聯(lián)合類型(Union Types)
說(shuō)起 聯(lián)合類型(Union Types) ,相信使用過(guò) Typescript 同學(xué)的一定對(duì)它又愛又恨:
定義函數(shù)入?yún)⒌臅r(shí)候,當(dāng)同一個(gè)位置的參數(shù)允許傳入多種參數(shù)類型,使用 聯(lián)合類型(Union Types)會(huì)非常的方便,但想智能地推導(dǎo)出返回值的類型地時(shí)候卻又犯了難。當(dāng)函數(shù)入?yún)€(gè)數(shù)不確定地時(shí)候,又不愿意寫出 (...args: any[]) => void這種毫無(wú)卵用的參數(shù)類型定義。使用 聯(lián)合類型(Union Types)時(shí),雖然有類型守衛(wèi)(Type guard),但是某些場(chǎng)景下依然不夠好用。
其實(shí)當(dāng)你對(duì)它有足夠的了解時(shí),你就會(huì)發(fā)現(xiàn) 聯(lián)合類型(Union Types) 比 交叉類型(Intersection Types) 不知道高到哪里去了,我和它談笑風(fēng)生。
類型系統(tǒng)中的 "數(shù)組"
下面就讓我們更加深入地了解一下 聯(lián)合類型(Union Types):
如何遍歷 聯(lián)合類型(Union Types) 呢?
既然目標(biāo)是 「批量操作類型」,自然少不了類型的 「遍歷」,和大多數(shù)編程語(yǔ)言方法一樣,在 Typescript 類型系統(tǒng)中也是 in 關(guān)鍵字來(lái)遍歷。
type?key?=?'vue'?|?'react';
type?MappedType?=?{?[k?in?key]:?string?}?//?{?vue:?string;?react:?string;?}
你看,通過(guò) ?in 關(guān)鍵字,我們可以很容易地遍歷 聯(lián)合類型(Union Types),并對(duì)類型作一些變換操作。
但有時(shí)候并不是所有所有 聯(lián)合類型(Union Types) 都是我們顯式地定義出來(lái)的。
我們想動(dòng)態(tài)地推導(dǎo)出 聯(lián)合類型(Union Types) 類型有哪些方法呢?
可以使用 keyof 關(guān)鍵字動(dòng)態(tài)地取出某個(gè)鍵值對(duì)類型的 key
interface?Student?{
??name:?string;
??age:?number;
}
type?studentKey?=?keyof?Student;?//?"name"?|?"age"
同樣的我們也可以通過(guò)一些方法取出 元組類型 子類型
type?framework?=?['vue',?'react',?'angular'];
type?frameworkVal1?=?framework[number];?//?"vue"?|?"react"?|?"angular"
type?frameworkVal2?=?framework[any];?//?"vue"?|?"react"?|?"angular"
實(shí)戰(zhàn)應(yīng)用
看到這里,有的同學(xué)可能要問(wèn)了,你既然說(shuō) 聯(lián)合類型(Union Types) 可以批量操作類型,「那我想把某一組類型批量映射成另一種類型,該怎么操作呢」?
方法其實(shí)有很多,這里提供一種思路,拋磚引玉一下,別的方法就留給同學(xué)們自行研究吧。
其實(shí)分析一下上面那個(gè)需求,不難看出,這個(gè)需求其實(shí)和數(shù)組的 map 方法有點(diǎn)相似
那么如何實(shí)現(xiàn)一個(gè)操作 聯(lián)合類型(Union Types) 的 map 函數(shù)呢?
//?這里的?placeholder?可以鍵入任何你所希望映射成為的類型
type?UnionTypesMap?=?T?extends?any???'placeholder'?:?never;
其實(shí)這里聰明的同學(xué)已經(jīng)看出來(lái),我們只是利用了 條件類型(Conditional Types),使其的判斷條件總是為 true,那么它就總是會(huì)返回左邊的類型,我們就可以拿到 泛型操作符 的入?yún)⒉⒆远x我們的操作。
讓我們趁熱打鐵,再舉個(gè)具體的栗子:把 「聯(lián)合類型(Union Types)」 的每一項(xiàng)映射成某個(gè)函數(shù)的 「返回值」。
type?UnionTypesMap2Func?=?T?extends?any???()?=>?T?:?never;
type?myUnionTypes?=?"vue"?|?"react"?|?"angular";
type?myUnionTypes2FuncResult?=?UnionTypesMap2Func;
//?(()?=>?"vue")?|?(()?=>?"react")?|?(()?=>?"angular")
相信有了上述內(nèi)容的學(xué)習(xí),我們已經(jīng)對(duì) 聯(lián)合類型(Union Types) 有了一個(gè)相對(duì)全面的了解,后續(xù)在此基礎(chǔ)之上在作一些高級(jí)的拓展,也如砍瓜切菜一般簡(jiǎn)單了。
其他數(shù)據(jù)類型
當(dāng)然除了數(shù)組,還存在其他的數(shù)據(jù)類型,例如可以用 type 或 interface 模擬 Javascript 中的 「字面量對(duì)象」,其特征之一就是可以使用 myType['propKey'] 這樣的方式取出子類型。這里拋磚引玉一下,有興趣的同學(xué)可以自行研究。
Typescript 的類型是支持 ?"作用域" 的
全局作用域
就像常見的編程語(yǔ)言一樣,在 Typescript 的類型系統(tǒng)中,也是支持 「全局作用域」 的。換句話說(shuō),你可以在沒(méi)有 「導(dǎo)入」 的前提下,在 「任意文件任意位置」 直接獲取到并且使用它。
通常使用 declare 關(guān)鍵字來(lái)修飾,例如我們常見的 圖片資源 的類型定義:
declare?module?'*.png';
declare?module?'*.svg';
declare?module?'*.jpg';
當(dāng)然我們也可以在 「全局作用域」 內(nèi)聲明一個(gè)類型:
declare?type?str?=?string;
declare?interface?Foo?{
??propA:?string;
??propB:?number;
}
需要注意的是,如何你的模塊使用了 export 關(guān)鍵字導(dǎo)出了內(nèi)容,上述的聲明方式可能會(huì)失效,如果你依然想要將類型聲明到全局,那么你就需要顯式地聲明到全局:
declare?global?{
??const?ModuleGlobalFoo:?string;
}
模塊作用域
就像 nodejs 中的模塊一樣,每個(gè)文件都是一個(gè)模塊,每個(gè)模塊都是獨(dú)立的模塊作用域。這里模塊作用域觸發(fā)的條件之一就是使用 export 關(guān)鍵字導(dǎo)出內(nèi)容。
每一個(gè)模塊中定義的內(nèi)容是無(wú)法直接在其他模塊中直接獲取到的,如果有需要的話,可以使用 import 關(guān)鍵字按需導(dǎo)入。
泛型操作符作用域&函數(shù)作用域
泛型操作符是存在作用域的,還記得這一章的第一節(jié)為了方便大家理解,我把泛型操作符類比為函數(shù)嗎?既然可以類比為函數(shù),那么函數(shù)所具備的性質(zhì),泛型操作符自然也可以具備,所以存在泛型操作符作用域自然也就很好理解了。
這里定義的兩個(gè)同名的 T 并不會(huì)相互影響:
type?TypeOperator?=?T;
type?TypeOperator2?=?T;
上述是關(guān)于泛型操作符作用域的描述,下面我們聊一聊真正的函數(shù)作用域:
「類型也可以支持閉包」:
function?Foo<T>?()?{
??return?function(param:?T)?{
????return?param;
??}
}
const?myFooStr?=?Foo<string>();
//?const?myFooStr:?(param:?string)?=>?string
//?這里觸發(fā)了閉包,類型依然可以被保留
const?myFooNum?=?Foo<number>();
//?const?myFooNum:?(param:?number)?=>?number
//?這里觸發(fā)了閉包,類型也會(huì)保持相互獨(dú)立,互不干涉
Typescript 的類型是支持 "遞歸" 的
Typescript 中的類型也是可以支持遞歸的,遞歸相關(guān)的問(wèn)題比較抽象,這里還是舉例來(lái)講解,同時(shí)為了方便大家的理解,我也會(huì)像第一節(jié)一樣,把類型遞歸的邏輯用 Javascript 語(yǔ)法描述一遍。
首先來(lái)讓我們舉個(gè)栗子:
假如現(xiàn)在需要把一個(gè)任意長(zhǎng)度的元組類型中的子類型依次取出,并用 & 拼接并返回。
這里解決的方法其實(shí)非常非常多,解決的思路也非常非常多,由于這一小節(jié)講的是 「遞歸」,所以我們使用遞歸的方式來(lái)解決。廢話不羅嗦,先上代碼:
//?shift?action
type?ShiftActionextends?any[]>?=?((...args:?T)?=>?any)?extends?((arg1:?any,?...rest:?infer?R)?=>?any)???R?:?never;
type?combineTupleTypeWithTecursion<T?extends?any[],?E?=?{}>?=?{
??1:?E,
??0:?combineTupleTypeWithTecursion<ShiftAction<T>,?E?&?T[0]>
}[T?extends?[]???1?:?0]
type?test?=?[{?a:?string?},?{?b:?number?}];
type?testResult?=?combineTupleTypeWithTecursion<test>;?//?{?a:?string;?}?&?{?b:?number;?}
看到上面的代碼是不是一臉懵逼?沒(méi)關(guān)系,接下來(lái)我們用普通的 Typescript 代碼來(lái) "翻譯" 一下上述的代碼。
function?combineTupleTypeWithTecursion(T:?object[],?E:?object?=?{}):?object?{
??return?T.length???combineTupleTypeWithTecursion(T.slice(1),?{?...E,?...T[0]?})?:?E
}
const?testData?=?[{?a:?'hello?world'?},?{?b:?100?}];
//?此時(shí)函數(shù)的返回值為?{?a:?'hello?world',?b:?100?}
combineTupleTypeWithTecursion(testData);
看到這兒,相信聰明的同學(xué)一下子就懂了,原來(lái)類型的遞歸與普通函數(shù)的遞歸本質(zhì)上是一樣的。如果觸發(fā)結(jié)束條件,就直接返回,否則就一直地遞歸調(diào)用下去,所傳遞的第二個(gè)參數(shù)用來(lái)保存上一次遞歸的計(jì)算結(jié)果。
當(dāng)然熟悉遞歸的同學(xué)都知道,常見的編程語(yǔ)言中,遞歸行為非常消耗計(jì)算機(jī)資源的,一旦超出了最大限制那么程序就會(huì)崩潰。同理類型中的遞歸也是一樣的,如果遞歸地過(guò)深,類型系統(tǒng)一樣會(huì)崩潰,所以這里的代碼大家理解就好,盡量不要在生產(chǎn)環(huán)境使用哈。
小結(jié)
還記得一開始提出的思考題嗎?其實(shí)通過(guò)上述的學(xué)習(xí),我們完全可以自信地說(shuō)出,Typescript 的 Type 本身也是一套完備的編程語(yǔ)言,甚至可以說(shuō)是完備的圖靈語(yǔ)言。因此類型本身也是可以用來(lái)編程的,你完全可以用它來(lái)編寫一些有趣的東西,更別說(shuō)是搞定日常開發(fā)中遇到的簡(jiǎn)單的業(yè)務(wù)場(chǎng)景了。
"高級(jí)用法" 的使用場(chǎng)景與價(jià)值
哪些用法可以被稱為 "高級(jí)用法"
其實(shí)所謂 "高級(jí)用法",不過(guò)是用來(lái)解決某些特定的場(chǎng)景而產(chǎn)生的特定的約定俗稱的寫法或者語(yǔ)法糖。那高級(jí)用法重要嗎?重要,也不重要。怎理解呢,根據(jù)編程中的 "二八原則",20%的知識(shí)儲(chǔ)備已經(jīng)可以解決80%的需求問(wèn)題,但是這剩余的20%,就是入門與熟練的分水嶺。
其實(shí)只要當(dāng)我們仔細(xì)翻閱一遍官方提供的 handbook[4],就已經(jīng)可以應(yīng)付日常開發(fā)了。但是就像本文一開頭說(shuō)的那樣,你是否覺(jué)得:
Typescript在某些場(chǎng)景下用起來(lái)很費(fèi)勁,遠(yuǎn)不及Javascript靈活度的十分之一。你是否為自己使用 Javascript中了某些 「騷操作」 用極簡(jiǎn)短的代碼解決了某個(gè)復(fù)雜的代碼而沾沾自喜,但卻為不正確的 「返回類型」 撓禿了頭。你是否明知用了若干 as xxx會(huì)讓你的代碼看起來(lái)很挫,但卻無(wú)能為力,含恨而終。
同學(xué),當(dāng)你使用某種辦法解決了上述的這些問(wèn)題,那么這種用法就可以被稱作 "高級(jí)用法"。
舉例說(shuō)明 "高級(jí)用法" 的使用場(chǎng)景
舉個(gè)栗子:在 Redux 中有一個(gè)叫作 combineReducers 的函數(shù),因?yàn)槟承﹫?chǎng)景,我們需要增加一個(gè) combineReducersParamFactory 的函數(shù),該函數(shù)支持傳入多個(gè)函數(shù),傳入函數(shù)的返回值為作為combineReducers 的入?yún)ⅲ覀冃枰隙鄠€(gè)入?yún)?shù)函數(shù)的返回值,并生成最終的對(duì)象供 combineReducers 函數(shù)使用。
思考一下邏輯,發(fā)現(xiàn)其實(shí)并不復(fù)雜,用 Javascript 可以很容易地實(shí)現(xiàn)出來(lái):
/**
?*?合并多個(gè)參數(shù)的返回?cái)?shù)值并返回
?*?@param?{?Function[]?}?reducerCreators
?*?@returns?{?Object?}
?*/
function?combineReducersParamFactory(...reducerCreators)?{
??return?reducerCreators.reduce((acc,?creator)?=>?({?...acc,?...creator()?}),?{})
}
//?test?...
function?todosReducer(state?=?[],?action)?{
??switch?(action.type)?{
????case?'ADD_TODO':
??????return?state.concat([action.text])
????default:
??????return?state
??}
}
function?counterReducer(state?=?0,?action)?{
??switch?(action.type)?{
????case?'INCREMENT':
??????return?state?+?1
????case?'DECREMENT':
??????return?state?-?1
????default:
??????return?state
??}
}
const?ret?=?combineReducersParamFactory(
??()?=>?({?todosReducer?}),
??()?=>?({?counterReducer?})
);
//?{?todosReducer:?[Function:?todosReducer],?counterReducer:?[Function:?counterReducer]?}
但如果用需要配備對(duì)應(yīng)的類型,應(yīng)該如何編寫呢?
type?Combine?=?(T?extends?any???(args:?T)?=>?any?:?never)?extends?(args:?infer?A)?=>?any???A?:?never;
/**
?*?合并多個(gè)參數(shù)的返回?cái)?shù)值并返回
?*?@param?{?Function[]?}?reducerCreators
?*?@returns?{?Object?}
?*/
function?combineReducersParamFactory<T?extends?((...args)?=>?object)[]>(...reducerCreators:?T):?Combine<ReturnType<T[number]>>?{
??return?reducerCreators.reduce<any>((acc,?creator)?=>?({?...acc,?...creator()?}),?{});
}
//?test?...
function?todosReducer(state:?object[],?action:?{?[x:?string]:?string})?{
??switch?(action.type)?{
????case?'ADD_TODO':
??????return?state.concat([action.text])
????default:
??????return?state
??}
}
function?counterReducer(state:?number,?action:?{?[x:?string]:?string})?{
??switch?(action.type)?{
????case?'INCREMENT':
??????return?state?+?1
????case?'DECREMENT':
??????return?state?-?1
????default:
??????return?state
??}
}
//?這里不需要顯示傳入類型,這里就可以得到正確的代碼提示
const?ret?=?combineReducersParamFactory(
??()?=>?({?todosReducer?}),
??()?=>?({?counterReducer?})
);
//?{?todosReducer:?[Function:?todosReducer],?counterReducer:?[Function:?counterReducer]?}
你看,類型經(jīng)過(guò)精心編排之后,就是可以讓調(diào)用者不增加任何負(fù)擔(dān)的前提下,享受到代碼提示的快樂(lè)。
小結(jié)
經(jīng)過(guò)這一章節(jié)的學(xué)習(xí),我們可以明確了解到,經(jīng)過(guò)我們精心編排的類型,可以變得非常的智能,可以讓調(diào)用者幾乎零成本地享受到代碼提示的快樂(lè)。或許在編排類型時(shí)所耗費(fèi)的時(shí)間成本比較大,但是一旦我們編排完成,就可以極大地減少調(diào)用者的腦力負(fù)擔(dān),讓調(diào)用者享受到編程的快樂(lè)。
類型推導(dǎo)與泛型操作符
流動(dòng)的類型(類型編寫思路)
熟悉 「函數(shù)式編程」 的同學(xué)一定對(duì) 「數(shù)據(jù)流動(dòng)」 的概念有較為深刻的理解。當(dāng)你在 "上游" 改變了一個(gè)值之后,"下游" 相關(guān)的會(huì)跟著自動(dòng)更新。有 「響應(yīng)式編程」 經(jīng)驗(yàn)的同學(xué)這是時(shí)候應(yīng)該迫不及待地想舉手了,同學(xué)把手放下,這里我們并不想深入地討論 「流式編程思想」,之所以引出這些概念,是想類比出本小節(jié)的重點(diǎn): 「流動(dòng)的類型」。
是的,編寫類型系統(tǒng)的思路是可以借鑒 「函數(shù)式編程」 的思想的。因此某一個(gè)類型發(fā)生變化時(shí),其他相關(guān)的類型也會(huì)自動(dòng)更新,并且當(dāng)代碼的臃腫到不可維護(hù)的時(shí)候,你會(huì)得到一個(gè)友好的提示,整個(gè)類型系統(tǒng)就好像一個(gè)被精心設(shè)計(jì)過(guò)的約束系統(tǒng)。
Typescript 代碼哲學(xué)
聊完了類型系統(tǒng)的編寫思路,咱們?cè)賮?lái)聊一聊代碼哲學(xué)。其實(shí)之所以現(xiàn)在 Typescript 越來(lái)越火,撇開哪些聊爛了的優(yōu)勢(shì)不談,其實(shí)最大的優(yōu)勢(shì)在于強(qiáng)大的類型表現(xiàn)能力,以及編輯器(VSCode)完備的代碼提示能力。
那么在這些優(yōu)勢(shì)的基礎(chǔ)上,我個(gè)人拓展了一些編碼哲學(xué)(習(xí)慣),這里見仁見智,大佬輕噴~:
減少不必要的顯式類型定義,盡可能多地使用類型推導(dǎo),讓類型的流動(dòng)像呼吸一樣自然。 盡可能少地使用 any或as any,注意這里并不是說(shuō)不能用,而是你判斷出目前情況下使用any是最優(yōu)解。如果確定要使用 any作為類型,優(yōu)先考慮一下是否可以使用unknown類型替代,畢竟any會(huì)破壞類型的流動(dòng)。盡可能少地使用 as xxx,如果大量使用這種方式糾正類型,那么大概率你對(duì) 「類型流動(dòng)」 理解的還不夠透徹。
常見類型推導(dǎo)實(shí)現(xiàn)邏輯梳理與實(shí)踐入門
類型的傳遞(流動(dòng))
前面我們說(shuō)到,類型是具備流動(dòng)性的,結(jié)合 「響應(yīng)式編程」 的概念其實(shí)很容易理解。這一小節(jié)我們將列舉幾個(gè)常見的例子,來(lái)和大家具體講解一下。
有編程經(jīng)驗(yàn)的同學(xué)都知道,數(shù)據(jù)是可以被傳遞的,同理,類型也可以。
你可用 type 創(chuàng)建一個(gè)類型指針,指向?qū)?yīng)的類型,那么就可以實(shí)現(xiàn)類型的傳遞,當(dāng)然你也可以理解為指定起一個(gè)別名,或者說(shuō)是拷貝,這里見仁見智,但是通過(guò)上述方法可以實(shí)現(xiàn)類型的傳遞,這是顯而易見的。
type?RawType?=?{?a:?string,?b:?number?};
//?這里就拿到了上述類型的引用
type?InferType?=?RawType;?//?{?a:?string,?b:?number?};
同樣,類型也可以隨著數(shù)據(jù)的傳遞而傳遞:
var?num:?number?=?100;
var?num2??=?num;
type?Num2Type?=?typeof?num2;?//?number
也正是依賴這一點(diǎn),Typescript 才得以實(shí)現(xiàn) 「類型檢查」、「定義跳轉(zhuǎn)」 等功能。
到這里熟悉 「流式編程」 的同學(xué)就要舉手了:你光說(shuō)了類型的 「?jìng)鬟f」,「輸入」 ?與 ?「輸出」,那我如果希望在類型 「?jìng)鬟f」 的過(guò)程中對(duì)它進(jìn)行操作,該怎么做呢?同學(xué)你不要急,這正是我下面所想要講的內(nèi)容。
類型的過(guò)濾與分流
在上一小節(jié)中,我們反復(fù)地扯到了 「函數(shù)式編程」、「響應(yīng)式編程」、「流式編程」 這些抽象的概念,其實(shí)并不是跑題,而是者兩者的思想(理念)實(shí)在太相似了,在本小節(jié)后續(xù)的講解中,我還會(huì)一直延用這些概念幫助大家理解。翻看一下常用 「函數(shù)式編程」 的庫(kù),不管是 Ramda 、RXJS 還是我們耳熟能詳?shù)?lodash 、underscore,里面一定有一個(gè)操作符叫作 filter,也就是對(duì)數(shù)據(jù)流的過(guò)濾。
這個(gè)操作符的使用頻率一定遠(yuǎn)超其他操作符,那么這么重要的功能,我們?cè)陬愋拖到y(tǒng)中該如何實(shí)現(xiàn)呢?
要解決這個(gè)問(wèn)題,這里我們先要了解一個(gè)在各大 技術(shù)社區(qū)/平臺(tái) 搜索頻率非常高的一個(gè)問(wèn)題:
「TypeScript中 的 ?never 類型具體有什么用?」
既然這個(gè)問(wèn)題搜索頻率非常之高,這里我也就不重復(fù)作答,有興趣的同學(xué)可以看一下尤大大的回答: TypeScript中的never類型具體有什么用? - 尤雨溪的回答 - 知乎[5]。
這里我們簡(jiǎn)單總結(jié)一下:
never代表空集。常用于用于校驗(yàn) "類型收窄" 是否符合預(yù)期,就是寫出類型絕對(duì)安全的代碼。 never常被用來(lái)作 "類型兜底"。
當(dāng)然上面的總結(jié)并不完整,但已經(jīng)足夠幫助理解本小節(jié)內(nèi)容,感興趣的同學(xué)可以自行查閱相關(guān)資料。
上面提到了 "類型收窄",這與我們的目標(biāo)已經(jīng)十分接近了,當(dāng)然我們還需要了解 never 參與類型運(yùn)算的相關(guān)表現(xiàn):
type?NeverTest?=?string?|?never?//?stirng
type?NeverTest2?=?string?&?never?//?never
重要的知識(shí)出現(xiàn)了:T | never,結(jié)果為 T。
看到這里,相信聰明的同學(xué)們已經(jīng)有思路了,我們可以用 never 來(lái)過(guò)濾掉 聯(lián)合類型(Union Types) 中不和期望的類型,其實(shí)這個(gè) 「泛型操作符」 早在 Typescript 2.8[6] 就已經(jīng)被加入到了官方文檔中了。
/**
?*?Exclude?from?T?those?types?that?are?assignable?to?U
?*/
type?Exclude?=?T?extends?U???never?:?T;
相信經(jīng)過(guò)這么長(zhǎng)時(shí)間的學(xué)習(xí),看到這里你一定很容易就能這種寫法的思路。
好了,講完了 「過(guò)濾」,我們?cè)賮?lái)講講 「分流」。類型 「分流」 的概念其實(shí)也不難理解,這個(gè)概念常常與邏輯判斷一同出現(xiàn),畢竟從邏輯層面來(lái)講,聯(lián)合類型(Union Types) 本質(zhì)上還是用來(lái)描述 「或」 的關(guān)系。同樣的概念如果引入到 「流式編程」 中,就自然而然地會(huì)引出 ?「分流」。換成打白話來(lái)講,就是不同數(shù)據(jù)應(yīng)被分該發(fā)到不同的 「管道」 中,同理,類型也需要。
那么這么常用的功能,在 Typescript 中如何處理呢?其實(shí)這種常見的問(wèn)題,官方也非常貼心地為我們考慮到了,那就是:類型守衛(wèi)(Type guard)。網(wǎng)上對(duì) 類型守衛(wèi)(Type guard) 有講解的文章非常的多,這里也不作贅述,有興趣的同學(xué)可以自行搜索學(xué)習(xí)。我們這里用一個(gè)簡(jiǎn)單的栗子簡(jiǎn)單地演示一下用法:
function?foo(x:?A?|?B)?{
??if?(x?instanceof?A)?{
????//?x?is?A
??}?else?{
??//?x?is?B
??}
}
「可以觸發(fā)類型守衛(wèi)的常見方式有」:typeof、instanceof、in、==、 ===、 !=、 !== 等等。
當(dāng)然在有些場(chǎng)景中,單單通過(guò)以上的方式不能滿足我們的需求,該怎么辦呢?其實(shí)這種問(wèn)題,官方也早已經(jīng)幫我考慮到了:使用 is 關(guān)鍵字自定義 類型守衛(wèi)(Type guard)。
//?注意這里需要返回?boolean?類型
function?isA(x):?x?is?A?{
??return?true;
}
//?注意這里需要返回?boolean?類型
function?isB(x):?x?is?B?{
??return?x?instanceof?B;
}
function?foo2(x:?unknown)?{
??if?(isA(x))?{
????//?x?is?A
??}?else?{
????//?x?is?B
??}
}
小結(jié)
這一章節(jié)中,我們通過(guò)類比 響應(yīng)式編程、流式編程 的概念方式,幫助大家更好地理解了 「類型推導(dǎo)」 的實(shí)現(xiàn)邏輯與思路,相信經(jīng)過(guò)了這一章節(jié)的學(xué)習(xí),我們對(duì) Typescript 中的類型推導(dǎo)又有了更加深入的理解。不過(guò)這一章引入的抽象的概念比較多,也比較雜,基礎(chǔ)不是太好的同學(xué)需要多花點(diǎn)時(shí)間翻看一下相關(guān)資料。
定制化擴(kuò)展你的 Typescript
Typescript Service Plugins 的產(chǎn)生背景、功能定位、基礎(chǔ)使用
產(chǎn)生背景
說(shuō)起 Typescript 的編譯手段大部分同學(xué)應(yīng)該都不會(huì)陌生,無(wú)論是在 webpack 中使用 ts-loader 或 babel-loader,還是在 gulp 中使用 gulp-typescript,亦或是直接使用 Typescript 自帶的命令行工具,相信大部分同學(xué)也都已經(jīng)駕輕就熟了,這里不做贅述。
這里我們把目光聚焦到擼碼體驗(yàn)上,相信有使用過(guò) Typescritp 開發(fā)前端項(xiàng)目的同學(xué)一定有過(guò)各種各樣的困擾,這里列舉幾個(gè)常見的問(wèn)題:
在處理 CSS Module 的樣式資源的類型定義時(shí),不滿足于使用 declare module '*.module.css'這種毫無(wú)卵用的類型定義。不想給編輯器安裝各種各樣的插件,下次啟動(dòng)編輯器的時(shí)間明顯變長(zhǎng),小破電腦不堪重負(fù),而且每次重裝系統(tǒng)都是一次噩夢(mèng)降臨。 不想妥協(xié)于同事的使用習(xí)慣,想使用自己熟悉的編輯器。 并不滿足于官方已有的代碼提示,想讓自己的編輯器更加地貼心與智能。
為了提供更加貼心的開發(fā)體驗(yàn),Typescript 官方提供一種解決思路——Typescript Service Plugins
功能定位
以下內(nèi)容摘自官方 WIKI:
?In TypeScript 2.2 and later, developers can enable language service plugins to 「augment the TypeScript code editing experience」.
?
其實(shí)官方文檔已經(jīng)寫的很清楚了,這玩意兒旨在優(yōu)化 Typescript 代碼的 「編寫體驗(yàn)」。所以想利用這玩意兒改變編譯結(jié)果或是想自創(chuàng)新語(yǔ)法的還是省省吧 嗯,我在說(shuō)我自己呢!
那么 Typescript Service Plugins 的可以用來(lái)做哪些事呢?
官方也有明確的回答:
?plugins are for augmenting the editing experience. Some examples of things plugins might do:
?
Provide errors from a linter inline in the editor
Filter the completion list to remove certain properties from window
Redirect "Go to definition" to go to a different location for certain identifiers
Enable new errors or completions in string literals for a custom templating language
同樣官方也給出了不推薦使用 Typescript Service Plugins 的場(chǎng)景:
?Examples of things language plugins cannot do:
?
Add new custom syntax to TypeScript
Change how the compiler emits JavaScript
Customize the type system to change what is or isn't an error when running tsc
好了,相信讀到這里大家一定對(duì) Typescript Service Plugins 有了一個(gè)大致的了解,下面我會(huì)介紹一下 ? ?Typescript Service Plugins 的安裝與使用。
如何安裝以及如何配置 Typescript Service Plugins
Typescript Service Plugins 的安裝方法
# 就像安裝普通的 `npm` 包一樣
npm install --save-dev your_plugin_name如何在 tsconfig.json 中配置 Typescript Service Plugins
{
?"compilerOptions": {
? ?/** compilerOptions Configuration ... */
? ?"noImplicitAny": true,
? ?"plugins": [
? ? ?{
? ? ? ?/** 配置插件名稱,也可以填寫本地路徑 */
? ? ? ?"name": "sample-ts-plugin"
? ? ? ?/** 這里可以給插件傳參 ... */
? ? ?}
? ? ?/** 支持同時(shí)引入多個(gè)插件 ... */
? ?]
?}
}幾個(gè)需要注意的地方:
如果使用 VSCode開發(fā),記得務(wù)必 using the workspace version of typescript[7],否則可能導(dǎo)致插件不生效。Typescript Service Plugins產(chǎn)生的告警或者報(bào)錯(cuò)不會(huì)影響編譯結(jié)果。如果配置完了不生效可以先嘗試重啟你的編輯器。
市面上已有的 Typescript Service Plugins 舉例介紹
?具體使用細(xì)節(jié)請(qǐng)用編輯器打開我提供的 demo,自行體驗(yàn)。
?
示例插件:typescript-plugin-css-modules[8]
插件安裝
npm install --save-dev typescript-styled-plugin typescript配置方法
在 tsconfig.json 中增加配置
{
?"compilerOptions": {
? ?"plugins": [
? ? ?{
? ? ? ?"name": "typescript-styled-plugin"
? ? ? ?/** 具體配置參數(shù)請(qǐng)查看官方文檔 */
? ? ?}
? ?]
?}
}插件基本介紹與使用場(chǎng)景
此插件可以用來(lái)緩解在使用 CSS Module 時(shí)沒(méi)有代碼提示的困境,主要思路就是通過(guò)讀取對(duì)應(yīng)的 CSS Module 文件并解析成對(duì)應(yīng)的 ?AST,并生成對(duì)應(yīng)的類型文件從而支持對(duì)應(yīng)的代碼提示。但是根據(jù)反饋來(lái)看,似乎某些場(chǎng)景下表現(xiàn)并不盡人意,是否值得大規(guī)模使用有待商榷。
類似實(shí)現(xiàn)思路的還有 typings-for-css-modules-loader[9],功能來(lái)說(shuō)肯定是 webpack loader 更加強(qiáng)大,但是 Typescript Plugin 更加輕量、入侵度也越低,取舍與否,見仁見智吧
示例插件:typescript-eslint-language-service[10]
插件安裝
npm install --save-dev eslint typescript-eslint-language-service配置方法
在 .eslintrc.* 文件中,添加對(duì)應(yīng)的 eslint 配置
在 tsconfig.json 中增加配置
{
?"compilerOptions": {
? ?"plugins": [
? ? ?{
? ? ? ?"name": "typescript-eslint-language-service"
? ? ? ?/** 默認(rèn)會(huì)讀取 `.eslintrc.*` 文件 */
? ? ? ?/** 具體配置參數(shù)請(qǐng)查看官方文檔 */
? ? ?}
? ?]
?}
}插件基本介紹與使用場(chǎng)景
此插件可以讓 Typescript 原生支持 eslint 檢查及告警,編輯器不需要安裝任何插件即可自持,但是報(bào)錯(cuò)并不影響編譯結(jié)果。
示例插件:typescript-styled-plugin[11]
插件安裝
npm install --save-dev typescript-styled-plugin typescript配置方法
在 tsconfig.json 中增加配置
{
?"compilerOptions": {
? ?"plugins": [
? ? ?{
? ? ? ?"name": "typescript-styled-plugin"
? ? ? ?/** 具體配置參數(shù)請(qǐng)查看官方文檔 */
? ? ?}
? ?]
?}
}插件基本介紹與使用場(chǎng)景
此插件可以為 styled-components[12] 的樣式字符串模板提供 屬性/屬性值 做語(yǔ)法檢查。 同時(shí)也推薦安裝 VSCode 插件 vscode-styled-components[13],為你的樣式字符串模板提供代碼提示以及語(yǔ)法高亮。
參考資料鏈接
Using the Compiler API[14] Using the Language Service API[15] Writing a Language Service Plugin[16] Useful Links for TypeScript Issue Management[17]
Q&A
可以利用 Typescript Service Plugin(例如配置 eslint 規(guī)則)阻塞編譯或者在編譯時(shí)告警嗎?
「答」:不可以,所有可以使用 Typescript Plugin 的場(chǎng)景一定都是編碼階段的,而且官方對(duì) plugins 的定位局限在了 只改善編寫體驗(yàn) 這方面,你并不能自定義語(yǔ)法或者自定義規(guī)則來(lái)改變編譯結(jié)果,不過(guò)你可以考慮使用自定義 compiler,當(dāng)然這是另一個(gè)話題了。
以下引用自官方文檔:
?TypeScript Language Service Plugins ("plugins") are for changing the 「editing experience only」. The core TypeScript language remains the same. Plugins can't add new language features such as new syntax or different typechecking behavior, and 「plugins aren't loaded during normal commandline typechecking or emitting」.
?
Reference
Typescript 官網(wǎng): https://www.typescriptlang.org/
[2]TypeScript Deep Dive: https://basarat.gitbook.io/typescript/
[3]TypeScript GitHub地址: https://github.com/microsoft/TypeScript
[4]handbook: https://www.typescriptlang.org/docs/handbook/basic-types.html
[5]TypeScript中的never類型具體有什么用? - 尤雨溪的回答 - 知乎: https://www.zhihu.com/question/354601204/answer/888551021
[6]Typescript 2.8: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types
[7]using the workspace version of typescript: https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-the-workspace-version-of-typescript
[8]typescript-plugin-css-modules: https://www.npmjs.com/package/typescript-plugin-css-modules
[9]typings-for-css-modules-loader: https://www.npmjs.com/package/@teamsupercell/typings-for-css-modules-loader
[10]typescript-eslint-language-service: https://www.npmjs.com/package/typescript-eslint-language-service
[11]typescript-styled-plugin: https://www.npmjs.com/package/typescript-styled-plugin
[12]styled-components: https://www.npmjs.com/package/styled-components
[13]vscode-styled-components: https://github.com/styled-components/vscode-styled-components
[14]Using the Compiler API: https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API
[15]Using the Language Service API: https://github.com/microsoft/TypeScript/wiki/Using-the-Language-Service-API
[16]Writing a Language Service Plugin: https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin
[17]Useful Links for TypeScript Issue Management: https://github.com/microsoft/TypeScript/wiki/Useful-Links-for-TypeScript-Issue-Management
推薦閱讀
1、力扣刷題插件
2、你不知道的 TypeScript 泛型(萬(wàn)字長(zhǎng)文,建議收藏)
4、immutablejs 是如何優(yōu)化我們的代碼的?
7、【校招面經(jīng)分享】好未來(lái)-北京-視頻面試
?關(guān)注加加,星標(biāo)加加~
?
如果覺(jué)得文章不錯(cuò),幫忙點(diǎn)個(gè)在看唄
