【TypeScript 演化史 — 1】non-nullable 的類型

  • 2019 年 11 月 13 日
  • 筆記

在這篇文章中,我們將討論發佈於 TypeScript 2.0 中的 non-nullable 類型,這是對類型系統的一個重大的改進,該特性可對 nullundefined 的檢查。cannot read property 'x' of undefinedundefined is not a function 在 JS 中是非常常見的錯誤,non-nullable 類型可以避免此類錯誤。

null 和 undefined 的值

TypeScript 2.0 之前,類型檢查器認為 nullundefined 是每種類型的有效值。基本上,nullundefined 可以賦值給任何東西。這包括基本類型,如字元串、數字和布爾值:

let name: string;  name = "Marius";  // OK  name = null;      // OK  name = undefined; // OK    let age: number;  age = 24;        // OK  age = null;      // OK  age = undefined; // OK    let isMarried: boolean;  isMarried = true;      // OK  isMarried = false;     // OK  isMarried = null;      // OK  isMarried = undefined; // OK

number 類型為例。它的域不僅包括所有的IEEE 754浮點數,而且還包括兩個特殊的值 nullundefined

對象、數組和函數類型也是如此。無法通過類型系統表示某個特定變數是不可空的。幸運的是,TypeScript 2.0 解決了這個問題。

嚴格的Null檢查

TypeScript 2.0 增加了對 non-nullable 類型的支援,並新增嚴格 null 檢查模式,可以通過在命令行上使用 ——strictNullChecks 標誌來選擇進入該模式。或者,可以在項目中的 tsconfig.json 文件啟用 strictnullcheck 啟用。

{    "compilerOptions": {      "strictNullChecks": true      // ...    }  }

在嚴格的 null 檢查模式中,nullundefined 不再分配給每個類型。nullundefined 現在都有自己的類型,每個類型只有一個值

如果咱們在編譯前時啟用了嚴格的 null 檢查,如果將 nullundefined 分配給任何變數都會導致類型錯誤

// 使用 --strictNullChecks 編譯    let name: string;  name = "Marius";  // OK  name = null;      // Error  name = undefined; // Error    let age: number;  age = 24;        // OK  age = null;      // Error  age = undefined; // Error    let isMarried: boolean;  isMarried = true;      // OK  isMarried = false;     // OK  isMarried = null;      // Error  isMarried = undefined; // Error

那麼,如何在 TypeScript 2.0 中使變數為空?

用聯合類型構建可空性

由於在啟用嚴格的 null 檢查時,類型在默認情況下是不可空的,所以我們需要顯式指定可為空,並告訴類型檢查器我們希望哪些變數為空。我們通過構造一個包含 nullundefined 類型的聯合類型來實現這一點

let name: string | null;  name = "Marius";  // OK  name = null;      // OK  name = undefined; // Error

注意,undefined 不是 name 變數的有效值,因為聯合類型不包含 undefined 類型

這種可空性方法的一大優點是,類型中哪些成員是可空的變得很明顯,並且可以自文檔化。以這個簡單的 User 類型為例:

type User = {    firstName: string;    lastName: string | undefined;  };    let jane: User = { firstName: "Jane", lastName: undefined };  let john: User = { firstName: "John", lastName: "Doe" };

我們可以通過添加 ?lastName 屬性設為可選。這樣就可以完全省略 las​​tName 屬性的定義。 此外,undefined 的類型會自動添加到聯合類型中。 因此,以下所有分配類型都是可以的:

type User = {    firstName: string;    lastName?: string;  };    // 可以為 lastName 屬性分配一個字元串  let john: User = { firstName: "John", lastName: "Doe" };    // 或者 undefined  let jane: User = { firstName: "Jane", lastName: undefined };    // 還可以省略  let jake: User = { firstName: "Jake" };

可為空類型的屬性訪問

如果對象的類型包括 nullundefined,則訪問任何屬性都會產生編譯時錯誤:

function getLength(s: string | null) {    // Error: Object 可能為空    return s.length;  }

在訪問屬性之前,需要使用類型保護來檢查給定對象上的屬性訪問是否安全:

function getLength(s: string | null) {    if (s === null) {      return 0;    }      return s.length;  }

TypeScript 是兼容 JS ,並支援條件表達式中的類型保護,所以這種方法也可以:

function getLength(s: string | null) {    return s ? s.length : 0;  }

使用可空類型的函數調用

如果試圖調用包含 nullundefined 類型的函數,則會產生編譯時錯誤。下面的callback 參數是可選的(注意?),所以它可能 undefined。因此,它不能被直接調用

function doSomething(callback?: () => void) {    // Error: 不能調用可能是 「undefined」 的對象    callback();  }

與在訪問屬性之前檢查對象類似,我們首先需要檢查函數是否具有非空值:

function doSomething(callback?: () => void) {    if (callback) {      callback();    }  }

還可以用 typeof 檢查返回的值

function doSomething(callback?: () => void) {    if (typeof callback === "function") {      callback();    }  }

總結

Non-nullable 類型是 TypeScript 類型系統的基礎和有價值的補充。它們允許對哪些變數和屬性可以為空進行精確構建。只有在類型保護將屬性訪問或函數調用確定為安全之後,才允許進行屬性訪問或函數調用,從而避免了許多編譯時的可空性錯誤。