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