摘要:比如或者都會(huì)導(dǎo)致函數(shù)返回值類型時(shí)。和特性一樣,等于是函數(shù)返回值中的或。注意對(duì)比下面的寫法對(duì)于,它的返回值是可迭代的對(duì)象,并且每個(gè)類型都是或者。首先是不支持方法重載的,是支持的,而類型系統(tǒng)一定程度在對(duì)標(biāo),當(dāng)然要支持這個(gè)功能。
1 引言
精讀原文是 typescript 2.0-2.9 的文檔:
2.0-2.8,2.9 草案.
我發(fā)現(xiàn),許多寫了一年以上 Typescript 開發(fā)者,對(duì) Typescript 對(duì)理解和使用水平都停留在入門階段。造成這個(gè)現(xiàn)象的原因是,Typescript 知識(shí)的積累需要 刻意練習(xí),使用 Typescript 的時(shí)間與對(duì)它的了解程度幾乎沒有關(guān)系。
這篇文章精選了 TS 在 2.0-2.9 版本中最重要的功能,并配合實(shí)際案例解讀,幫助你快速跟上 TS 的更新節(jié)奏。
對(duì)于 TS 內(nèi)部?jī)?yōu)化的用戶無(wú)感部分并不會(huì)羅列出來(lái),因?yàn)檫@些優(yōu)化都可在日常使用過程中感受到。
2 精讀由于 Typescript 在嚴(yán)格模式下的許多表現(xiàn)都與非嚴(yán)格模式不同,為了避免不必要的記憶,建議只記嚴(yán)格模式就好了!
嚴(yán)格模式導(dǎo)致的大量邊界檢測(cè)代碼,已經(jīng)有解了直接訪問一個(gè)變量的屬性時(shí),如果這個(gè)變量是 undefined,不但屬性訪問不到,js 還會(huì)拋出異常,這幾乎是業(yè)務(wù)開發(fā)中最高頻的報(bào)錯(cuò)了(往往是后端數(shù)據(jù)異常導(dǎo)致的),而 typescript 的 strict 模式會(huì)檢查這種情況,不允許不安全的代碼出現(xiàn)。
在 2.0 版本,提供了 “非空斷言標(biāo)志符” !. 解決明確不會(huì)報(bào)錯(cuò)的情況,比如配置文件是靜態(tài)的,那肯定不會(huì)拋出異常,但在 2.0 之前的版本,我們可能要這么調(diào)用對(duì)象:
const config = { port: 8000 }; if (config) { console.log(config.port); }
有了 2.0 提供的 “非空斷言標(biāo)志符”,我們可以這么寫了:
console.log(config!.port);
在 2.8 版本,ts 支持了條件類型語(yǔ)法:
type TypeName= T extends string ? "string"
當(dāng) T 的類型是 string 時(shí),TypeName 的表達(dá)式類型為 "string"。
這這時(shí)可以構(gòu)造一個(gè)自動(dòng) “非空斷言” 的類型,把代碼簡(jiǎn)化為:
console.log(config.port);
前提是框架先把 config 指定為這個(gè)特殊類型,這個(gè)特殊類型的定義如下:
export type PowerPartial= { [U in keyof T]?: T[U] extends object ? PowerPartial : T[U] };
也就是 2.8 的條件類型允許我們?cè)陬愋团袛噙M(jìn)行遞歸,把所有對(duì)象的 key 都包一層 “非空斷言”!
此處?kù)`感來(lái)自 egg-ts 總結(jié)增加了 never object 類型
當(dāng)一個(gè)函數(shù)無(wú)法執(zhí)行完,或者理解為中途中斷時(shí),TS 2.0 認(rèn)為它是 never 類型。
比如 throw Error 或者 while(true) 都會(huì)導(dǎo)致函數(shù)返回值類型時(shí) never。
和 null undefined 特性一樣,never 等于是函數(shù)返回值中的 null 或 undefined。它們都是子類型,比如類型 number 自帶了 null 與 undefined 這兩個(gè)子類型,是因?yàn)槿魏斡蓄愋偷闹刀加锌赡苁强眨ㄒ簿褪菆?zhí)行期間可能沒有值)。
這里涉及到很重要的概念,就是預(yù)定義了類型不代表類型一定如預(yù)期,就好比函數(shù)運(yùn)行時(shí)可能因?yàn)?throw Error 而中斷。所以 ts 為了處理這種情況,將 null undefined 設(shè)定為了所有類型的子類型,而從 2.0 開始,函數(shù)的返回值類型又多了一種子類型 never。
TS 2.2 支持了 object 類型, 但許多時(shí)候我們總把 object 與 any 類型弄混淆,比如下面的代碼:
const persion: object = { age: 5 }; console.log(persion.age); // Error: Property "age" does not exist on type "object".
這時(shí)候報(bào)錯(cuò)會(huì)出現(xiàn),有時(shí)候閉個(gè)眼改成 any 就完事了。其實(shí)這時(shí)候只要把 object 刪掉,換成 TS 的自動(dòng)推導(dǎo)就搞定了。那么問題出在哪里?
首先 object 不是這么用的,它是 TS 2.3 版本中加入的,用來(lái)描述一種非基礎(chǔ)類型,所以一般用在類型校驗(yàn)上,比如作為參數(shù)類型。如果參數(shù)類型是 object,那么允許任何對(duì)象數(shù)據(jù)傳入,但不允許 3 "abc" 這種非對(duì)象類型:
declare function create(o: object | null): void; create({ prop: 0 }); // 正確 create(null); // 正確 create(42); // 錯(cuò)誤 create("string"); // 錯(cuò)誤 create(false); // 錯(cuò)誤 create(undefined); // 錯(cuò)誤
而一開始 const persion: object 這種用法,是將能精確推導(dǎo)的對(duì)象類型,擴(kuò)大到了整體的,模糊的對(duì)象類型,TS 自然無(wú)法推斷這個(gè)對(duì)象擁有哪些 key,因?yàn)閷?duì)象類型僅表示它是一個(gè)對(duì)象類型,在將對(duì)象作為整體觀察時(shí)是成立的,但是 object 類型是不承認(rèn)任何具體的 key 的。
增加了修飾類型TS 在 2.0 版本支持了 readonly 修飾符,被它修飾的變量無(wú)法被修改。
在 TS 2.8 版本,又增加了 - 與 + 修飾修飾符,有點(diǎn)像副詞作用于形容詞。舉個(gè)例子,readonly 就是 +readonly,我們也可以使用 -readonly 移除只讀的特性;也可以通過 -?: 的方式移除可選類型,因此可以延伸出一種新類型:Required
type Required可以定義函數(shù)的 this 類型= { [P in keyof T]-?: T[P] };
也是 TS 2.0 版本中,我們可以定制 this 的類型,這個(gè)在 vue 框架中尤為有用:
function f(this: void) { // make sure `this` is unusable in this standalone function }
this 類型是一種假參數(shù),所以并不會(huì)影響函數(shù)真正參數(shù)數(shù)量與位置,只不過它定義在參數(shù)位置上,而且永遠(yuǎn)會(huì)插隊(duì)在第一個(gè)。
引用、尋址支持通配符了簡(jiǎn)單來(lái)說(shuō),就是模塊名可以用 * 表示任何單詞了:
declare module "*!text" { const content: string; export default content; }
它的類型可以輻射到:
import fileContent from "./xyz.txt!text";
這個(gè)特性很強(qiáng)大的一個(gè)點(diǎn)是用在拓展模塊上,因?yàn)榘?tsconfig.json 的模塊查找也支持通配符了!舉個(gè)例子一下就懂:
最近比較火的 umi 框架,它有一個(gè) locale 插件,只要安裝了這個(gè)插件,就可以從 umi/locale 獲取國(guó)際化內(nèi)容:
import { locale } from "umi/locale";
其實(shí)它的實(shí)現(xiàn)是創(chuàng)建了一個(gè)文件,通過 webpack.alias 將引用指了過去。這個(gè)做法非常棒,那么如何為它加上類型支持呢?只要這么配置 tsconfig.json:
{ "compilerOptions": { "paths": { "umi/*": ["umi", ""] } } }
將所有 umi/* 的類型都指向
TS 在 2.x 支持了許多新 compileOptions,但 skipLibCheck 實(shí)在是太耀眼了,筆者必須多帶帶提出來(lái)說(shuō)。
skipLibCheck 這個(gè)屬性不但可以忽略 npm 不規(guī)范帶來(lái)的報(bào)錯(cuò),還能最大限度的支持類型系統(tǒng),可謂一舉兩得。
拿某 UI 庫(kù)舉例,某天發(fā)布的小版本 d.ts 文件出現(xiàn)一個(gè)漏洞,導(dǎo)致整個(gè)項(xiàng)目構(gòu)建失敗,你不再需要提 PR 催促作者修復(fù)了!skipLibCheck 可以忽略這種報(bào)錯(cuò),同時(shí)還能保持類型的自動(dòng)推導(dǎo),也就是說(shuō)這比 declare module "ui-lib" 將類型設(shè)置為 any 更強(qiáng)大。
對(duì)類型修飾的增強(qiáng)TS 2.1 版本可謂是針對(duì)類型操作革命性的版本,我們可以通過 keyof 拿到對(duì)象 key 的類型:
interface Person { name: string; age: number; } type K1 = keyof Person; // "name" | "age"
基于 keyof,我們可以增強(qiáng)對(duì)象的類型:
type NewObjType= { [P in keyof T]: T[P] };
Tips:在 TS 2.8 版本,我們可以以表達(dá)式作為 keyof 的參數(shù),比如 keyof (A & B)。
Tips:在 TS 2.9 版本,keyof 可能返回非 string 類型的值,因此從一開始就不要認(rèn)為 keyof 的返回類型一定是 string。
NewObjType 原封不動(dòng)的將對(duì)象類型重新描述了一遍,這看上去沒什么意義。但實(shí)際上我們有三處拓展的地方:
左邊:比如可以通過 readonly 修飾,將對(duì)象的屬性變成只讀。
中間:比如將 : 改成 ?:,將對(duì)象所有屬性變成可選。
右邊:比如套一層 Promise
基于這些能力,我們拓展出一系列上層很有用的 interface:
Readonly
Partial
Pick
Extract
Record
Exclude
Omit
NonNullable
ReturnType
InstanceType
以上類型都內(nèi)置在 lib.d.ts 中,不需要定義就可直接使用,可以認(rèn)為是 Typescript 的 utils 工具庫(kù)。
多帶帶拿 ReturnType 舉個(gè)例子,體現(xiàn)出其重要性:
Redux 的 Connect 第一個(gè)參數(shù)是 mapStateToProps,這些 Props 會(huì)自動(dòng)與 React Props 聚合,我們可以利用 ReturnType
TS 2.3 版本做了許多對(duì) Generators 的增強(qiáng),但實(shí)際上我們?cè)缫延?async/await 替代了它,所以 TS 對(duì) Generators 的增強(qiáng)可以忽略。需要注意的一塊是對(duì) for..of 語(yǔ)法的異步迭代支持:
async function f() { for await (const x of fn1()) { console.log(x); } }
這可以對(duì)每一步進(jìn)行異步迭代。注意對(duì)比下面的寫法:
async function f() { for (const x of await fn2()) { console.log(x); } }
對(duì)于 fn1,它的返回值是可迭代的對(duì)象,并且每個(gè) item 類型都是 Promise 或者 Generator。對(duì)于 fn2,它自身是個(gè)異步函數(shù),返回值是可迭代的,而且每個(gè) item 都不是異步的。舉個(gè)例子:
function fn1() { return [Promise.resolve(1), Promise.resolve(2)]; } function fn2() { return [1, 2]; }
在這里順帶一提,對(duì) Array.map 的每一項(xiàng)進(jìn)行異步等待的方法:
await Promise.all( arr.map(async item => { return await item.run(); }) );
如果為了執(zhí)行順序,可以換成 for..of 的語(yǔ)法,因?yàn)閿?shù)組類型是一種可迭代類型。
泛型默認(rèn)參數(shù)了解這個(gè)之前,先介紹一下 TS 2.0 之前就支持的函數(shù)類型重載。
首先 JS 是不支持方法重載的,Java 是支持的,而 TS 類型系統(tǒng)一定程度在對(duì)標(biāo) Java,當(dāng)然要支持這個(gè)功能。好在 JS 有一些偏方實(shí)現(xiàn)偽方法重載,典型的是 redux 的 createStore:
export default function createStore(reducer, preloadedState, enhancer) { if (typeof preloadedState === "function" && typeof enhancer === "undefined") { enhancer = preloadedState; preloadedState = undefined; } }
既然 JS 有辦法支持方法重載,那 TS 補(bǔ)充了函數(shù)類型重載,兩者結(jié)合就等于 Java 方法重載:
declare function createStore( reducer: Reducer, preloadedState: PreloadedState, enhancer: Enhancer ); declare function createStore(reducer: Reducer, enhancer: Enhancer);
可以清晰的看到,createStore 想表現(xiàn)的是對(duì)參數(shù)個(gè)數(shù)的重載,如果定義了函數(shù)類型重載,TS 會(huì)根據(jù)函數(shù)類型自動(dòng)判斷對(duì)應(yīng)的是哪個(gè)定義。
而在 TS 2.3 版本支持了泛型默認(rèn)參數(shù),可以某些場(chǎng)景減少函數(shù)類型重載的代碼量,比如對(duì)于下面的代碼:
declare function create(): Container; declare function create (element: T): Container ; declare function create ( element: T, children: U[] ): Container ;
通過枚舉表達(dá)了范型默認(rèn)值,以及 U 與 T 之間可能存在的關(guān)系,這些都可以用泛型默認(rèn)參數(shù)解決:
declare function create( element?: T, children?: U ): Container ;
尤其在 React 使用過程中,如果用泛型默認(rèn)值定義了 Component:
.. Component..
就可以實(shí)現(xiàn)以下等價(jià)的效果:
class Component extends React.PureComponent動(dòng)態(tài) Import{ //... } // 等價(jià)于 class Component extends React.PureComponent { //... }
TS 從 2.4 版本開始支持了動(dòng)態(tài) Import,同時(shí) Webpack4.0 也支持了這個(gè)語(yǔ)法(在 精讀《webpack4.0%20 升級(jí)指南》 有詳細(xì)介紹),這個(gè)語(yǔ)法就正式可以用于生產(chǎn)環(huán)境了:
const zipUtil = await import("./utils/create-zip-file");
準(zhǔn)確的說(shuō),動(dòng)態(tài) Import 實(shí)現(xiàn)于 webpack 2.1.0-beta.28,最終在 TS 2.4 版本獲得了語(yǔ)法支持。
在 TS 2.9 版本開始,支持了 import() 類型定義:
const zipUtil: typeof import("./utils/create-zip-file") = await import("./utils/create-zip-file")
也就是 typeof 可以作用于 import() 語(yǔ)法,而不真正引入 js 內(nèi)容。不過要注意的是,這個(gè) import("./utils/create-zip-file") 路徑需要可被推導(dǎo),比如要存在這個(gè) npm 模塊、相對(duì)路徑、或者在 tsconfig.json 定義了 paths。
好在 import 語(yǔ)法本身限制了路徑必須是字面量,使得自動(dòng)推導(dǎo)的成功率非常高,只要是正確的代碼幾乎一定可以推導(dǎo)出來(lái)。好吧,所以這也從另一個(gè)角度推薦大家放棄 require。
Enum 類型支持字符串從 Typescript 2.4 開始,支持了枚舉類型使用字符串做為 value:
enum Colors { Red = "RED", Green = "GREEN", Blue = "BLUE" }
筆者在這提醒一句,這個(gè)功能在純前端代碼內(nèi)可能沒有用。因?yàn)樵?TS 中所有 enum 的地方都建議使用 enum 接收,下面給出例子:
// 正確 { type: monaco.languages.types.Folder; } // 錯(cuò)誤 { type: 75; }
不僅是可讀性,enum 對(duì)應(yīng)的數(shù)字可能會(huì)改變,直接寫 75 的做法存在風(fēng)險(xiǎn)。
但如果前后端存在交互,前端是不可能發(fā)送 enum 對(duì)象的,必須要轉(zhuǎn)化成數(shù)字,這時(shí)使用字符串作為 value 會(huì)更安全:
enum types { Folder = "FOLDER" } fetch(`/api?type=${monaco.languages.types.Folder}`);數(shù)組類型可以明確長(zhǎng)度
最典型的是 chart 圖,經(jīng)常是這樣的二維數(shù)組數(shù)據(jù)類型:
[[1, 5.5], [2, 3.7], [3, 2.0], [4, 5.9], [5, 3.9]]
一般我們會(huì)這么描述其數(shù)據(jù)結(jié)構(gòu):
const data: string[][] = [[1, 5.5], [2, 3.7], [3, 2.0], [4, 5.9], [5, 3.9]];
在 TS 2.7 版本中,我們可以更精確的描述每一項(xiàng)的類型與數(shù)組總長(zhǎng)度:
interface ChartData extends Array自動(dòng)類型推導(dǎo){ 0: number; 1: number; length: 2; }
自動(dòng)類型推導(dǎo)有兩種,分別是 typeof:
function foo(x: string | number) { if (typeof x === "string") { return x; // string } return x; // number }
和 instanceof:
function f1(x: B | C | D) { if (x instanceof B) { x; // B } else if (x instanceof C) { x; // C } else { x; // D } }
在 TS 2.7 版本中,新增了 in 的推導(dǎo):
interface A { a: number; } interface B { b: string; } function foo(x: A | B) { if ("a" in x) { return x.a; } return x.b; }
這個(gè)解決了 object 類型的自動(dòng)推導(dǎo)問題,因?yàn)?object 既無(wú)法用 keyof 也無(wú)法用 instanceof 判定類型,因此找到對(duì)象的特征吧,再也不要用 as 了:
// Bad function foo(x: A | B) { // I know it"s A, but i can"t describe it. (x as A).keyofA; } // Good function foo(x: A | B) { // I know it"s A, because it has property `keyofA` if ("keyofA" in x) { x.keyofA; } }4 總結(jié)
Typescript 2.0-2.9 文檔整體讀下來(lái),可以看出還是有較強(qiáng)連貫性的。但我們可能并不習(xí)慣一步步學(xué)習(xí)新語(yǔ)法,因?yàn)樾抡Z(yǔ)法需要時(shí)間消化、同時(shí)要連接到以往語(yǔ)法的上下文才能更好理解,所以本文從功能角度,而非版本角度梳理了 TS 的新特性,比較符合學(xué)習(xí)習(xí)慣。
另一個(gè)感悟是,我們也許要用追月刊漫畫的思維去學(xué)習(xí)新語(yǔ)言,特別是 TS 這種正在發(fā)展中,并且迭代速度很快的語(yǔ)言。
5 更多討論討論地址是:精讀《Typescript2.0 - 2.9》 · Issue #85 · dt-fe/weekly
如果你想?yún)⑴c討論,請(qǐng)點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/95308.html
摘要:引言發(fā)布了幾個(gè)新特性,主要變化是類型檢查更嚴(yán)格,對(duì)一些時(shí)髦功能拓展了類型支持。精讀這次改動(dòng)意圖非常明顯,是為了跟上的新語(yǔ)法。基本可以算是對(duì)社區(qū)的回饋。討論地址是精讀新特性如果你想?yún)⑴c討論,請(qǐng)點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。 1 引言 Typescript 3.2 發(fā)布了幾個(gè)新特性,主要變化是類型檢查更嚴(yán)格,對(duì) ES6、ES7 一些時(shí)髦功能拓展了類型支持。 2 概要 下面挑一...
摘要:前端進(jìn)階進(jìn)階構(gòu)建項(xiàng)目一配置最佳實(shí)踐狀態(tài)管理之痛點(diǎn)分析與改良開發(fā)中所謂狀態(tài)淺析從時(shí)間旅行的烏托邦,看狀態(tài)管理的設(shè)計(jì)誤區(qū)使用更好地處理數(shù)據(jù)愛彼迎房源詳情頁(yè)中的性能優(yōu)化從零開始,在中構(gòu)建時(shí)間旅行式調(diào)試用輕松管理復(fù)雜狀態(tài)如何把業(yè)務(wù)邏輯這個(gè)故事講好和 前端進(jìn)階 webpack webpack進(jìn)階構(gòu)建項(xiàng)目(一) Webpack 4 配置最佳實(shí)踐 react Redux狀態(tài)管理之痛點(diǎn)、分析與...
摘要:精讀原文介紹了學(xué)習(xí)源碼的兩個(gè)技巧,并利用實(shí)例說(shuō)明了源碼學(xué)習(xí)過程中可以學(xué)到許多周邊知識(shí),都讓我們受益匪淺。討論地址是精讀源碼學(xué)習(xí)如果你想?yún)⑴c討論,請(qǐng)點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。 1. 引言 javascript-knowledge-reading-source-code 這篇文章介紹了閱讀源碼的重要性,精讀系列也已有八期源碼系列文章,分別是: 精讀《Immer.js》源...
摘要:引言本周精讀的文章是,看看作者是如何解釋這個(gè)多態(tài)性含義的。讀完文章才發(fā)現(xiàn),文章標(biāo)題改為的多態(tài)性更妥當(dāng),因?yàn)檎恼露荚谡f(shuō),而使用場(chǎng)景不局限于。更多討論討論地址是精讀的多態(tài)性如果你想?yún)⑴c討論,請(qǐng)點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。 1 引言 本周精讀的文章是:surprising-polymorphism-in-react-applications,看看作者是如何解釋這個(gè)多態(tài)性含...
摘要:引言本周精讀的文章是,講了如何利用實(shí)現(xiàn)串行執(zhí)行。總結(jié)串行隊(duì)列一般情況下用的不多,因?yàn)榇袝?huì)阻塞,而用戶交互往往是并行的。更多討論討論地址是精讀用實(shí)現(xiàn)串行執(zhí)行如果你想?yún)⑴c討論,請(qǐng)點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。 1 引言 本周精讀的文章是 why-using-reduce-to-sequentially-resolve-promises-works,講了如何利用 reduce...
閱讀 2155·2021-10-08 10:15
閱讀 1185·2019-08-30 15:52
閱讀 514·2019-08-30 12:54
閱讀 1531·2019-08-29 15:10
閱讀 2682·2019-08-29 12:44
閱讀 3008·2019-08-29 12:28
閱讀 3348·2019-08-27 10:57
閱讀 2212·2019-08-26 12:24