2021年與TypeScript愉快玩耍

本文原創:nanyifei

前言

近幾年,前端領域得到了日新月異的發展,各種新技術、框架層出不窮,前端的圈子越來越大。在 Github 的官方統計中,JavaScript 已經連續多年在語言榜上拔得頭籌。隨著應用越做越大,業務越來越復雜,動態語言自然有其靈活性好的優點,但是同時會因為無法保證合理的類型而引起不必要的并且隱藏的 bug。并且在業務代碼流轉的時候,如果代碼沒有清晰的注釋,很難閱讀。

jsDoc + eslint 配合 vscode 使用能夠減少許多閱讀代碼的不安和不適情緒,但是規則約束過于嚴格常常會出現因為不及時寫注釋而帶來的大面積飄紅。注釋雖然可以解決一部分代碼難以理解的問題,畢竟由于是靜態的文本,注釋無法靈活的流動

1.jpg

TypeScript 在繼承了 JavaScript 所有特性的基礎上,給原生 js 插上了靜態類型及增強的面向對象能力的翅膀,并且持續推進部分 es 提案語法成為標準。在打敗了 FlowCoffeeScript 等一眾對手后,成為社區的主流。在 Github 的年度語言榜上一路高歌,2020 年已經排到了第 4 位,并且還有持續進步之勢。

社區內優秀的作品數不勝數,為什么要寫這篇文章呢?我們受到了一種啟發:在科學研究領域,有一類 paper 專門對一個研究點好的研究現狀進行匯總,方便其他相關工作者能夠了解該領域的最佳實踐,即綜述。我們想做的事情類似,把每一個前端領域的點相關的好文章整理起來,逐漸匯聚成面,以期望形成完備的知識體系,把最優秀的資源分享給大家。

這是一個開始,也是在2021年第一個月。第一篇讓我們與 TypeScript 愉快玩耍。

基礎篇

類型系統

從語法層面上講,TypeScript 可以做如下理解:

TypeScript = JavaScript + 類型系統 + 增強的面向對象語法 + ES語法提案 + 編譯器

總的來說,如果開發者有扎實的 JavaScript 基礎,那么 Ts 上手會很快。

首先是類型系統,Ts 引入了 BooleanNumberStringSymbolArrayEnumAnyUnknownTupleVoidNever 等類型。類型系統繁多復雜,但是每種類型都有其存在的價值。

這里先推薦一些文檔及文章,從中就可以窺探類型系統的秘密。

學習的一個好方法就是結合官方提供的在線編譯器 Playground 與一篇詳盡的文章。官方的 HandBook 自然是最全面直觀的一手資料,當然 Github 及社區內也有很多優秀的文章及開源項目介紹 Ts,這里不勝列舉。

在上述這些類型中,有許多需要我們額外關注的細節點:

枚舉類型

我們都知道枚舉類型有數字、字符串、異構及常量枚舉。

  • 尤其需要注意的是,常量枚舉是由 const 關鍵字修飾的枚舉,在編譯階段被刪除,也就是說 常量枚舉無法編譯出任何 js 代碼。小伙伴們可以在 Playground 上親自試一試。
const enum Directions {
    Up,
    Down,
    Left,
    Right
}
// 對應編譯后的 js 代碼只有嚴格模式的聲明語句
"use strict";
  • 除了字符串枚舉,編譯后都會生成一個雙向的映射關系。這就意味著如果我們使用 Object.entries 去獲取枚舉對象的鍵值對時,會有如下的結果:
enum Directions {
    Up = 0,
    Down = 1,
    Left = 2,
    Right = 3
}
const DirectionsList = Object.entries(Directions)
// [["0", "Up"], ["1", "Down"], ["2", "Left"], ["3", "Right"], ["Up", 0], ["Down", 1], ["Left", 2], ["Right", 3]] 

可以看到雙向映射關系會產生對稱的鍵值關系。如果改成字符串枚舉,就會有如下的結果:

enum Directions {
    Up = '0',
    Down = '1',
    Left = '2',
    Right = '3'
}
const DirectionsList = Object.entries(Directions)
// [["Up", "0"], ["Down", "1"], ["Left", "2"], ["Right", "3"]] 

少了雙向映射,就可以用枚舉存儲一些常見的 select 組件要求的內容了。

頂級類型和兜底類型

ts 中有兩個頂級類型 anyunknown,還有一個兜底類型 never。當然 any 類型是類型系統的逃逸倉,也可以為所有類型兜底。大量使用 any 類型會帶來不可控制的結果,ts 帶來的靜態類型檢查機制無法生效,因此 TypeScript 3.0 引入了 unknown 類型。

顧名思義,unknown 是“未知”的含義,即任何明確的類型都可以賦值給 unknown 類型,而不能把 unknown 類型的變量賦值給任何一個有明確類型的變量。unknown 類型可以理解為:如果一個變量類型暫時無法確定,但是它在未來的某一時刻一定會有明確的類型,我們就可以在初始化該變量的時候,給一個 unknown 類型。因此其使用也需要明確一個原則:使用前,要對其做類型判斷,否則會語法報錯。我們也可以在各大框架的源代碼中看到 unknown 的身影。

// Vue3 源碼中關于 unknown 的使用
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
  
function createRef(rawValue: unknown, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

這里更多的細節可以參考知乎問答:寫TypeScript時,什么時候用any?什么時候用unkown?有沒有規律或準則?

另一方面,其實需要投入更多關注點的是兜底類型 never,直觀意義上講用于:

  • 拋出異常的函數
  • 無限循環

實際上,never 類型是所有類型的子類型,廣泛應用于工具類型中,主要用途為 discriminated union,即類型收窄,可以幫助我們寫出類型更安全的代碼。這里更多細節可以參考:

Object object {}

這是三種容易混淆的類型:

  • object 是比較常用的類型,區別于基本類型,用于表示非原始類型。
  • {} 是一種比較特殊的類型,不需要顯式地給類型定義,當初始化一個變量為 {},ts 會自動推斷該變量的類型為 {}。類似于 Object.freeze 的能力,不能直接對變量進行屬性值的操作。

有關三者的詳細區別請查看 一文讀懂 TS 中 Object, object, {} 類型之間的區別

斷言

斷言的作用是讓開發者告知 ts 一些相關變量的類型信息,可以分為類型斷言、非空斷言、確定賦值斷言及 const 斷言。

const 斷言

在 Ts3.4 中引入了一種新的斷言 const assertions,使用了該斷言后,字面量不能被擴展。變量的類型被約束在當前字面量的形狀上,并且屬性只讀。可以使用 as const<const> 兩種方式來聲明。

// type 的類型為 {text: string}
const type = { text: "hello" } as const;

Vue3 中,組件的 props 可以結合 PropType 進行 ts 類型聲明。如果我們需要抽離一部分公共的 props 存儲到 ts 文件中,在使用的時候在組件內引入,那么這個 props 對象就需要使用 const 斷言來保證其只讀的特性。可以在 vue-next 源碼中 runtime-core/src/apiDefineComponent.ts 里關于 defineComponent 源碼中也可以發現這樣的特征。

// overload 4: object format with object props declaration
// see `ExtractPropTypes` in ./componentProps.ts
export function defineComponent<
  // props 是只讀的
  PropsOptions extends Readonly<ComponentPropsOptions>,
  RawBindings,
  D,
  C extends ComputedOptions = {},
  M extends MethodOptions = {},
  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
  E extends EmitsOptions = Record<string, any>,
  EE extends string = string
>(
  ...
): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE>

同時 const 斷言還在 redux 的使用中有應用,具體詳情請閱讀:

面向類型編程

Ts 不僅僅從語法層面提供了靜態類型檢查機制,更是給了開發者更加廣闊的空間將代碼寫的更加靈活,提升寫代碼的幸福感與安全感。引入了 ts 之后,就是以一種思維方法去開發需求,詳情查看 TypeScript - 一種思維方式。如何讓應用中的類型流動起來,能夠復用甚至衍生,同時盡可能保證應用類型安全對于開發者而言是一個不斷成長的過程。在這其中,泛型起到了至關重要的作用。

泛型

泛型是一種描述程序中存在的類型及類型之間關系的方式,泛型單獨存在是沒有意義的,正如其名稱一樣,太廣泛了,因此需要配合泛型約束,通過合理的約束能夠寫出健壯性很強的代碼。

只要是用 ts 寫的第三方庫中,都可以隨處看到泛型的身影。而在類型編程過程中,對類型取其索引簽名、做類型映射、類型修飾等等操作也如家常便飯一般常見,因此 ts 及社區提供了很多好用的工具類型,將類型編程演繹到極致。

工具類型

在工具類型中我們可以經常看到 never 類型的身影,用于幫助在條件類型中將類型收窄。

在工具類型中,我們還會經常見到 infer 關鍵字的身影,后面跟上一個待推斷的類型。這里有一個延遲推斷的思想,與 js 中回調函數的思想類似,只有 ts 獲取到足夠的類型信息后才能推斷出類型。常見的 infer 使用在 ReturnType 這個工具類型中,用于獲取函數類型的返回值類型。

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

這個工具類型可以做如下解釋:如果 T 這個類型滿足了 (...args: any[]) => infer R 這個函數類型的所有形狀要求,而且函數的返回值類型能夠被推斷出來,那么函數的返回值類型為 R,否則類型推斷結果為最寬泛的類型 any

有了這樣子的思想,infer 還可以做更多有意思的事情,比如:在一個 Tuple 類型中,刪除掉第一個位置的元素。就可以如下做:

type Tail<T extends any[]> = ((...args: T) => void) extends (a: any, ...args: infer P) => void ? P : never;
type A = [string, number, boolean];
type AT = Tail<A>
const at: AT = [1, false];

這個想法來源于我們在函數的 arguments 對象上進行提前解構,拿到第一個入參及后面的所有入參。如果類型推斷可以推斷出解構后的參數形狀,那么就可以得到我們想要的結果。同時,這里一定要注意一個細節點,如果 Tail 這個工具類型定義為如下:

// 對比上面,我們僅僅去掉了 extends 之前這個函數形狀的括號
type Tail<T extends any[]> = (...args: T) => void extends (a: any, ...args: infer P) => void ? P : never;

如上定義會一直返回一個 never 類型,原因在于如果不加括號,ts 的條件類型推斷會變成 void extends (a: any, ...args: infer P) => void,這顯然是不可能滿足的,因此會一直返回 never 類型。

應用篇

這一部分中,我們列舉了部分社區內好的技術實踐,既有在熱門框架的基礎應用,也有優秀的工程師的技術沉淀,部分鏈接可能需要梯子訪問。

React

在 Class Component 中的應用

在 Hooks 中的應用

Vue

Vue2 中使用裝飾器與 Class Components 方式。

Vue3 的 Composition Api 提供了更加優雅的邏輯復用能力,因此相關的邏輯工具包是學習 Vue3ts 的好資料,并且可以為簡化邏輯代碼,為業務賦能。

Node

Ts 在 node 端的應用主要圍繞裝飾器展開,即便 js 中的裝飾器與 ts 中的裝飾器存在諸多差異。但是有了裝飾器,就可以將元編程、依賴注入的思想應用進去。

裝飾器相關

框架

  • Midway
  • Nest
  • Overnight
  • Loopback

結語

TypeScript 是未來前端的大勢所趨,社區活躍,用戶量大,因此能夠產出大量的高質量庫及技術文章。本文嘗試以一種綜述形式,將在學習過程中閱讀過的印象深刻,有深度啟發的好文章分享出來,希望能夠對大家的學習有一定的幫助,同時也感謝以上附錄文章作者的杰出貢獻。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • Any application that can be written in Javascript, will e...
    前端精閱讀 477評論 0 3
  • 官方handbook[https://www.typescriptlang.org/docs/handbook/i...
    ShoneSingLone閱讀 595評論 0 0
  • 使用ts寫React代碼寫了將近三個月,從剛開始覺得特別垃圾到現在覺得沒有ts不行的一些實踐以及思考。 如果按部就...
    小哪吒閱讀 31,129評論 1 9
  • 2019 9月 [工具] TreeJS 在線編輯器: https://threejs.org/editor/[ht...
    halber閱讀 2,058評論 0 2
  • 一、簡介 1.1 什么是 TypeScript TypeScript 是 JavaScript 的一個超集,主要提...
    _ihhu閱讀 1,273評論 0 2