TypeScript - 一種思惟方式

摘要: 學會TS思考方式。javascript

Fundebug經受權轉載,版權歸原做者全部。html

電影《降臨》中有一個觀點,語言會影響人的思惟方式,對於前端工程師來講,使用 typescript 開發無疑就是在嘗試換一種思惟方式作事情。前端

其實直到最近,我纔開始系統的學習 typescript ,先後大概花了一個月左右的時間。在這以前,我也在一些項目中模仿他人的寫法用過 TS,不過平心而論,在這一輪系統的學習以前,我並不理解 TS。一個多月前,我理解的 TS 是一種能夠對類型進行約束的工具,可是如今才發現 TS 並不簡單是一個工具,使用它,會影響我寫代碼時的思考方式。java

TS 怎麼影響了個人思考方式

對前端開發者來講,TS 能強化了「面向接口編程」這一理念。咱們知道稍微複雜一點的程序都離不開不一樣模塊間的配合,不一樣模塊的功能理應是更爲清晰的,TS 能幫咱們梳理清不一樣的接口。node

明確的模塊抽象過程

TS 對個人思考方式的影響之一在於,我如今會把考慮抽象和拓展看做寫一個模塊前的必備環節了。固然一個好的開發者用任何語言寫程序,考慮抽象和拓展都會是一個必備環節,不過若是你在平常生活中使用過清單,你就會明白 TS 經過接口將這種抽象明確爲具體的內容的意義所在了,任何沒有被明確的內容,其實都有點像是可選的內容,每每就容易被忽略。react

舉例來講,好比說咱們用 TS 定義一個函數,TS 會要求咱們對函數的參數及返回值有一個明確的定義,簡單的定義一些類型,卻能幫助咱們定位函數的做用,好比說咱們設置其返回值類型爲 void ,就明確的代表了咱們想利用這個函數的反作用;git

把抽象明確下來,對後續代碼的修改也很是有意義,咱們不用再擔憂忘記了以前是怎麼構想的呢,對多人協做的團隊來講,這一點也許更爲重要。github

固然使用 jsdoc 等工具也能把對函數的抽象明確下來,不過並無那麼強制,因此效果不必定會很好,不過 jsdoc 反而能夠作爲 TS 的一種補充。typescript

更自信的寫代碼

TS 還能讓我更自信的寫前端代碼,這種自信來自 TS 能夠幫咱們避免不少可能因爲本身的忽略形成的 bug。實際上,關於 TS 輔助避免 bug 方面存在專門的研究,一篇名爲 To Type or Not to Type: Quantifying Detectable Bugs in JavaScript 的論文,代表使用 TS 進行靜態類型檢查能幫咱們至少減小 15% 以上的 bug (這篇論文的研究過程也頗有意思,感興趣能夠點擊連接閱讀)。編程

能夠舉一個例子來講明,TS 是怎麼給我帶來這種自信的。

下面這條語句,你們都很熟悉,是 DOM 提供依據 id 獲取元素的方法。

const a = document.getElementById("a")
複製代碼

對我本身來講,使用 TS 以前,我忽略了document.getElementById的返回值還多是 null,這種不經意的忽略也許在將來就會形成一個意想不到的 bug。

使用 TS,在編輯器中就會明確的提醒咱們 a 的值可能爲 null

咱們並不必定要處理值 null 的狀況,使用 const a = document.getElementById('id')!能夠明確告訴 TS ,它不會是 null,不過至少,這時候咱們清楚的知道本身想作什麼。

使用 TS 的過程就是一種學習的過程

使用 TS 後,感受本身經過瀏覽器查文檔的時間明顯少了不少。不管是庫仍是原生的 js 或者 nodejs,甚至是本身團隊其它成員定義的類型。結合 VSCode ,會有很是智能的提醒,也能夠很方便看到相應的接口的確切定義。使用的過程就是在加深理解的過程,確實「面向接口編程」自然和靜態類型更爲親密。

好比說,咱們使用 Color 這個庫,VSCode 會有下面這類提醒:

2019-05-11-002

不用去查文檔,咱們就能看到其提供的 API。 若是咱們去看這個庫的源文件會發現,能有提醒的緣由在於存在下面這樣的定義:

// @types/color/index.d.TS
interface Color {
    toString(): string;
    toJSON(): Color;
    string(places?: number): string;
    percenTString(places?: number): string;
    array(): number[];
    object(): { alpha?: number } & { [key: string]: number };
    unitArray(): number[];
    unitObject(): { r: number, g: number, b: number, alpha?: number };
    ...
}
複製代碼

這種提醒無疑能加強開發的效率,雖然定義類型在早期會花費必定的時間,可是對於一個長期維護的比較大型的項目,使用 TS 很是值得。

一種學習 typescript 的路徑

也許是由於,我以前從未系統的學習過一門靜態語言,因此從開始學到感受本身基本入門了 TS 花的精力還挺多的。 學習 TS 的過程當中,主要參考瞭如下這些資料,你能夠直接點擊連接查看,也能夠繼續看後文,我對這些資料有着一些簡單的分析。

在閱讀上述資料的過程當中,我使用 TS 重寫了一個基於 CRA 的簡單可是很完整的前端項目,如今以爲,使用 TS 來開發工做中的常見需求,應該都能應對了。若是你是剛剛開始學 TS,不妨參照下面的路徑學習。

搭建 TS 運行環境

不要誤解,並不是從零搭建。學習實踐性很強的內容時,邊學邊練習能夠幫咱們更快的掌握。若是你使用 React,藉助 yarn 或者 create-react-app,可輕易的構造一個基於 TS 的項目。

在命令行中執行下述命令便可生產可直接使用的項目:

# 使用 yarn
$ yarn create react-app TS-react-playground --typescript
# 使用 npx
$ npx create-react-app TS-react-playground --typescript
複製代碼

隨後若是須要,能夠在tsconfig.json中添加額外的配置。

就我我的而言,我喜歡同步配置 TS-lint 與 prettier,已免去以後練習過程當中格式的煩惱。配置方法能夠參考 Configure TypeScript, TSLint, and Prettier in VS Code for React Native Development 這篇文章,或者看個人配置記錄

若是你不使用 React,TypeScript 官方文檔首頁就提供了 TS 配合其它框架的使用方法。

理解關鍵的概念

我一直以爲,學習一項新的技能,清楚其邊界很重要,相關的細節知識則能夠在後續的使用過程當中逐步的瞭解。咱們都知道,TS 是 JS 的超集,因此學習 TS 的第一件事情就是要找到「超」的邊界在哪裏。

這個階段,推薦閱讀 TypeScript handbook — book,這本書其實也是官方推薦的入門手冊。這裏給的連接是中文翻譯版的連接,翻譯的質量很是好,雖然內容沒有英文官方文檔新,不過學習新的東西最好仍是從本身最熟悉的內容入手,因此不妨先看中文文檔。閱讀過程當中遇到的示例,均可以在上面搭建的 TS-playground 中練習一下,熟悉一下。

TS 作爲 JS 的超集,其「超」其實主要在兩方面

  • TS 爲 JS 引入了一套類型系統;
  • TS 支持一些非 ECMAScript 正式標準的語法,好比裝飾器;

關於第二點,TS 作的事情有些相似 babel,因此也有人說 TS 是 babel 最大的威脅。不過這些新語法,極可能你早就使用過,本文再也不贅述。

比較難理解的實際上是這套類型系統,這套類型系統有着本身的聲明空間(Declaration Spaces),具備本身的一些關鍵字和語法。

對我來講,學習 TS 最大的難點就在於這套類型系統中有着一些我以前不多瞭解的概念,在這裏能夠大體的梳理一下。

一些 TS 中的新概念

編程實際上就是對數據進行操做和加工的過程。類型系統能輔助咱們對數據進行更爲準確的操做。TypeScript 的核心就在於其提供一套類型系統,讓咱們對數據類型有所約束。約束有時候很簡單,有時候很抽象。

TS 支持的類型以下:boolean,number,string,[],Tuple,enum,any,void,null,undefined,never,Object

TS 中更復雜的數據結構其實都是針對上述類型的組合,關於類型的基礎知識,推薦先閱讀基礎類型一節,這裏只討論最初對我形成困擾的概念:

  • enum: 如今想一想 enum 枚舉類型很是實用,不少其它的語言都內置了這一類型,合理的使用枚舉,能讓咱們的代碼可讀性更高,好比:
const enum MediaTypes {
  JSON = "application/json"
}

fetch("https://swapi.co/api/people/1/", {
  headers: {
      Accept: MediaTypes.JSON
  }
})
.then((res) => res.json())
複製代碼
  • never: never 表明代碼永遠不會執行到這裏,經常能夠應用在 switch casedefault中,防止咱們遺漏 case 未處理,好比:
enum ShirTSize {
  XS,
  S,
  M,
  L,
  XL
}

function assertNever(value: never): never {
  console.log(Error(`Unexpected value '${value}'`));
}

function prettyPrint(size: ShirTSize) {
  switch (size) {
      case ShirTSize.S: console.log("small");
      case ShirTSize.M: return "medium";
      case ShirTSize.L: return "large";
      case ShirTSize.XL: return "extra large";
        // case ShirTSize.XS: return "extra small";
      default: return assertNever(size);
  }
}
複製代碼

下面是上述代碼在個人編輯器中的截圖,編輯器會經過報錯告知咱們還有未處理的狀況。

  • 類型斷言: 類型斷言其實就是你告訴編譯器,某個值具有某種類型。有兩種不一樣的方式能夠添加類型斷言:
  • <string>someValue
  • someValue as string

關於類型斷言,我看文檔時的疑惑點在於,我想不到什麼狀況下會使用它。後來發現,當你知道有這麼一個功能,在實際使用過程當中,就會發現能用得着,好比說遷移遺留項目時。

  • Generics(泛型): 泛型讓咱們的數據結構更爲抽象可複用,由於這種抽象,也讓它有時候不是那麼好理解。泛型的應用場景很是普遍,好比:
type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};
複製代碼

可以讓某一種接口的子類型均可覺得 null。我記得我第一次看到泛型時也以爲它很很差理解,不事後來多用了幾回後,就以爲還好了。

  • interface 和 type interfacetype 均可以用來定義一些複雜的類型結構,最不少狀況下是通用的,最初我一直沒能理解它們兩者之間區別在哪裏,後來發現,兩者的區別在於:
    • interface建立了一種新的類型,而 type 僅僅是別名,是一種引用;
    • 若是 type 使用了 union operator (|) 操做符,則不能將 type implements 到 class 上;
    • 若是 type 使用了 union(|) 操做符 ,則不能被用以 extends interface
    • type 不能像 interface 那樣合併,其在做用域內惟一; [1]

在視頻 Use Types vs. Interfaces from @volkeron on @eggheadio 中,經過實例對兩者的區別有更細緻的說明。

值得指出的是,TypeScript handbook 關於 type 和 interface 的區別還停留在 TS 2.0 版本,對應章節如今的描述並不許確,想要詳細瞭解,可參考 Interface vs Type alias in TypeScript 2.7這篇文章。

  • 類型保護 TS 編譯器會分析咱們的程序併爲某一個變量在指定的做用域來指明儘量確切的類型,類型保護就是一種輔助肯定類型的方法,下面的語句均可以用做類型保護:
    • typeof padding === "number"
    • padder instanceof SpaceRepeatingPadder

一個應用實例是結合 redux 中的 reducer 中依據不一樣的 type,TS 能分別出不一樣做用域內 action 應有的類型。

  • 類型映射 類型映射是 TypeScript 提供的從舊類型中建立新類型的一種方式。它們很是實用。好比說,咱們想要快速讓某個接口中的全部屬性變爲可選的,能夠按照下面這樣寫:
interface Person {
    name: string;
    age: number;
}
type PartialPerson = { [P in keyof Person]?: Person[P] }
複製代碼

還有一個概念叫作 映射類型,TS 內置一些映射類型(其實是一些語法糖),讓咱們能夠方便的進行類型映射。好比經過內置的映射類型 Partial ,上面的表達式能夠按照下面這樣寫:

interface Person {
    name: string;
    age: number;
}
type PartialPerson = Partial<Person>
複製代碼

常見的映射類型,能夠參看這篇文章 — TS 一些工具泛型的使用及其實現,除了作爲語法糖內置在 TS 中的映射類型(如Readonly),這篇文章中也提到了一些未內置最 TS 中可是很實用的映射類型(好比 Omit)。

  • 第三方的庫,如何獲得類型支持 咱們很難保證,第三方的庫都原生支持 TS 類型,在你使用過一段時間 TS 後,你確定安裝過相似 @types/xxx 的類型庫,安裝相似這樣的庫,實際上就安裝了某個庫的描述文件,對於這些第三方庫的類型的定義,都存儲在DefinitelyTyped 這個倉庫中,經常使用的第三方庫在這裏面都有定義了。在 TypeSearch 中能夠搜索第三方庫的類型定義包。

關於類型,還有一些不少其它的知識點,不過一些沒有那麼經常使用,一些沒有那麼難理解,在此暫不贅述。

消化學到的新概念

我首次看完《TypeScript handbook》時,確實以爲本身懂了很多,可是發現動手寫代碼,仍是會常常卡住。追其緣由,可能在於一會兒接收了太多的新概念,一些概念並無來得及消化,這時候我推薦看下面這門網課:

看視頻算是一種比較輕鬆的學習方式,這門課時長大概是一個小時。會把 TypeScript handbook 這本書中的一些比較重要的概念,配合實例講解一次。能夠跟着教程把示例敲一次,在 vscode 中多看看給出的提示,看完以後,對 TS 的一些核心概念,確定會有更深的理解。

模仿和實踐

想要真的掌握 TS,少不了實踐。模仿也是一種好的實踐方式,已 React + TypeScript 爲例,比較推薦的模仿內容以下:

  1. TypeScript-React-Starter ,這是微軟爲 TS 初學者提供的一個很是好的資料,能夠繼續使用咱們上面構建的 playground ,參照這個倉庫的 readme 寫一次,差很少就能知道 TS 結合 React 的基本用法了;
  2. GitHub - react-typescript-cheaTSheet,這個教程也比較簡單,不過上面那個教程更近了一步,依據其 readme 繼續改造咱們的 playground 後,咱們能知道,React + Redux + TypeScript 該如何配合使用;
  3. react-redux-typescript-guide ,這個教程則展現了基於 TypeScript 如何應用一些更復雜的模式,咱們也能夠模仿其提供的用法,將其應用到咱們本身的項目中;
  4. Ultimate React Component Patterns with Typescript 2.8 ,這篇文章則能夠作爲上述內容的補充,其在掘金上有漢語翻譯,點贊量很是高,看完以後,差很少就能瞭解到若是使用 TS 應對各類 React 組件模式了。
  5. Use TypeScript to develop React Applications — egghead.io,隨後若是想再輕鬆一點,則能夠再看看這個網課,跟着別人的講解,回頭看看本身模仿着寫的一些代碼,也許會有不一樣的感觸;

至此,你確定就已經具有了基礎的 TS 開發能力,能夠獨立的結合 TS 開發相對複雜的應用了。

更深的理解

固然也許你並不會知足於會用 TS,你還想知道 TS 的工做原理是什麼。這時候推薦閱讀下面兩篇內容:

關於 TS 的原理,我尚未來得及仔細去看。不過 AST 在前端中的應用還真是多,待我補充更多的相關知識後,也許會對 AST 有一個更全面的總結。

TS 固然也不是沒有缺點,The TypeScript Tax [2] 是一篇很是優秀的文章,閱讀這篇文章能讓咱們更爲客觀看待 TS,雖然站在做者的角度看,TS 弊大於利,主要緣由是 TS 提供的功能大多均可以用其它工具配合在必定程度上代替,並且類型系統會須要寫太多額外的代碼,類型系統在必定程度上也破壞了動態語言的靈活性,讓一些動態語言特有的模式很難在其中被應用。做者最終的結論帶有很強的主觀色彩,我並非很是承認,可是這篇文章的分析過程很是精彩,就 TS 的各類特性和如今的 JS 生態進行了對比,能讓咱們對 TS 有一個更全面的瞭解,很是推薦閱讀,也許你會和我同樣,看完這個分析過程,會對 TS 更感興趣。

TS 每隔幾個月就會發佈一個新的小版本,每一個小版本在 TypeScript 官方博客[3] 上都會有專門的說明,可用用做跟進學習 TS 的參考。

推薦閱讀

下述參考內容在文中,都有連接,若是都看過,則無需再重複查看了。

參考

相關文章
相關標籤/搜索