【TypeScript 演化史 -- 10】更好的空值檢查 和 混合類

做者:Marius Schulz
譯者:前端小智
來源: https://mariusschulz.com/
點贊再看,養成習慣

本文 GitHub https://github.com/qq44924588... 上已經收錄,更多往期高贊文章的分類,也整理了不少個人文檔,和教程資料。歡迎Star和完善,你們面試能夠參照考點複習,但願咱們一塊兒有點東西。前端

更好地檢查表達式的操做數中的 null/undefined

在TypeScript 2.2中,空檢查獲得了進一步的改進。TypeScript 如今將帶有可空操做數的表達式標記爲編譯時錯誤。git

具體來講,下面這些會被標記爲錯誤:github

  • 若是+運算符的任何一個操做數是可空的,而且兩個操做數都不是anystring類型。
  • 若是-***/%<<>>>>>, &, |^運算符的任何一個操做數是可空的。
  • 若是 <><=>=in 運算符的任何一個操做數是可空的。
  • 若是 instanceof 運算符的右操做數是可空的。
  • 若是一元運算符+-~++或者--的操做數是可空的。

來看看若是我們不當心,可空表達式操做數就會坑下我們的狀況。在 TypeScript 2.2 以前,下面這個函數是能夠很好地編譯經過的:面試

function isValidPasswordLength(
  password: string,
  min: number,
  max?: number
) {
  return password.length >= min && password.length <= max;
}

注意max參數是可選的。這意味着我們可使用兩個或三個參數來調用isValidPasswordLengthtypescript

isValidPasswordLength("open sesame", 6, 128); // true
isValidPasswordLength("open sesame", 6, 8); // false

密碼 "open sesame"的長度爲10個字符。所以,對於長度範圍 [6,128] 返回 true,對於長度範圍[6,8]返回false,到目前爲止,一切 ok。express

若是調用isValidPasswordLength且不提供max參數值,那麼當密碼長度超過 min 值時,我們可能但願返回 true。然而,事實並不是如此:segmentfault

isValidPasswordLength("open sesame", 6); // false

這裏的問題在於 <= max 比較。若是maxundefined,那麼 <= max 的值永遠都爲false。在這種狀況下,isValidPasswordLength將永遠不會返回true框架

在 TypeScript 2.2 中,表達式password.length <= max不正確的類型,若是你的應用程序正在嚴格的null檢查模式下運行:函數

function isValidPasswordLength(
  password: string,
  min: number,
  max?: number
) {
  return password.length >= min  && password.length <= max; // Error: 對象可能爲「未定義」.
}
若是操做數的類型是 nullundefined或者包含 nullundefined的聯合類型,則操做數視爲可空的。

注意:包含nullundefined的聯合類型只會出如今--strictNullChecks模式中,由於常規類型檢查模式下nullundefined在聯合類型中是不存在的。工具

那麼要怎麼修正這個問題呢?一種的解決方案是爲max參數提供一個默認值,它只在傳遞undefined 時起做用。這樣,該參數仍然是可選的,但始終包含類型爲number的值

function isValidPasswordLength(
  password: string,
  min: number,
  max: number = Number.MAX_VALUE
) {
  return password.length >= min && password.length <= max;
}

固然我們也能夠選擇其餘的方法,可是我以爲這個方法很好。只要再也不將maxundefined 的值進行比較,就能夠了

混合類

TypeScript 的一個目的是支持不一樣框架和庫中使用的通用 JS 模式。從TypeScript 2.2開始,增長了對 ES6 混合類(mixin class)模式。接下來說講 mixin 是什麼,而後舉例說明了如何在 TypeScript 中使用它們。

JavaScript/TypeScript中的 mixin

混合類是實現不一樣功能方面的類。其餘類能夠包含 mixin 並訪問它的方法和屬性。這樣,mixin 提供了一種基於組合行爲的代碼重用形式。

混合類指一個extends(擴展)了類型參數類型的表達式的類聲明或表達式. 如下規則對混合類聲明適用:

  • extends表達式的類型參數類型必須是混合構造函數.
  • 混合類的構造函數 (若是有) 必須有且僅有一個類型爲any[]的變長參數, 而且必須使用展開運算符在super(...args)調用中將這些參數傳遞。

定義完成以後,來研究一些代碼。下面是一個 Timestamped 函數,它在timestamp 屬性中跟蹤對象的建立日期:

type Constructor<T = {}> = new (..args: any[]) => T;

function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    timestamp = Date.now()
  }
}

這看起來有點複雜,我們一行一行來看看:

type Constructor<T = {}> = new (..args: any[]) => T;

type Constructor <T>是構造簽名的別名,該簽名描述了能夠構造通用類型T的對象的類型,而且其構造函數接受任意數量的任何類型的參數。

接下來,讓咱們看一下mixin函數自己:

function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    timestamp = Date.now();
  };
}

Timestamped 函數接受一個名爲Base的參數,該參數屬於泛型類型 TBase注意TBase 必須與Constructor兼容,即類型必須可以構造某些東西。

在函數體中,我們建立並返回一個派生自Base的新類。這種語法乍一看可能有點奇怪。我們建立的是類表達式,而不是類聲明,後者是定義類的更經常使用方法。我們的新類定義了一個timestamp的屬性,並當即分配自UNIX時代以來通過的毫秒數。

注意,從mixin函數返回的類表達式是一個未命名的類表達式,由於class關鍵字後面沒有名稱。與類聲明不一樣,類表達式沒必要命名。我們能夠選擇添加一個名稱,它將是類主體的本地名稱,並容許類引用本身

function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class Timestamped extends Base {
    timestamp = Date.now();
  };
}

如今已經介紹了兩個類型別名和mixin函數的聲明,接下來看看如何在另外一個類中使用 mixin:

class User {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

// 經過將`"Timestamped"`混合到"User"中建立一個新類
const TimestampedUser = Timestamped(User);

// 實例化新的 "TimestampedUser" 類
const user = new TimestampedUser("前端小智")

// 如今,我們能夠同時從User 類中訪問屬性
// 也能夠從 Timestamped 類中訪問屬性
console.log(user.name);
console.log(user.timestamp);

TypeScript 編譯器知道咱們在這裏建立並使用了一個mixin,一切都是徹底靜態類型的,而且會自動完成和重構。

混合構造函數

如今,看看一個稍微高級一點的 mixin,類中定義一個構造函數

function Tagged<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    tag: string | null;

    constructor(...args: any[]) {
      super(...args);
      this.tag = null;
    }
  };
}

若是在混合類中定義構造函數,那麼它必須有一個類型爲any[]rest參數。這樣作的緣由是,mixin不該該綁定到具備已知構造函數參數的特定類;所以,mixin應該接受任意數量的任意值做爲構造函數參數。全部參數都傳遞給Base的構造函數,而後mixin執行它的任務。在我們的例子中,它初始化 tag 屬性。

混合構造函數類型指僅有單個構造函數簽名,且該簽名僅有一個類型爲 any[] 的變長參數,返回值爲對象類型. 好比, 有 X 爲對象類型, new (...args: any[]) => X 是一個實例類型爲 X 的混合構造函數類型。

之前面使用Timestamped的相同方式來使用混合Tagged

// 經過 User 做爲混合 Tagged 來建立一個新類
const TaggedUser = Tagged(User);

// 實例化 "TaggedUser" 類
const user = new TaggedUser("John Doe");

// 如今,能夠從 User 類訪問屬性和 Tagged 中的屬性

user.name = "Jane Doe";
user.tag = "janedoe";

mixin 與方法

到目前爲止,我們只在mixin中添加了數據屬性。如今來看看另外一個 mixin,它額外實現了兩個方法:

fucntion Activatable<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    isActivated = false;
    
    activate() {
      this.isActivated = true;
    }
    
    deactivate() {
      this.isActivated = false;
    }
  }
}

我們從mixin函數返回一個常規的類。這意味着我們可使用全部受支持的類功能,例如構造函數,屬性,方法,getter/setter,靜態成員等。

如何所示,我們如何在 User 類中使用混合的 Activatable

const ActivatableUser = Activatable(User);

// 實例化新的"ActivatableUser"類
const user = new ActivatableUser("John Doe");

//初始化,isActivated 的值爲 false
console.log(user.isActivated);

user.activate();

console.log(user.isActivated); // true

組合多個mixin

組合的mixin,可讓它更加靈活。一個類能夠包含任意多的mixin,爲了演示這點,我們把上面提到的全部mixin 代碼組合在一塊兒。

const SpecialUser = Activatable(Tagged(Timestamped(User)));
const user = new SpecialUser("John Doe");

固然 SpecialUser類不必定很是有用,但關鍵是,TypeScript靜態地理解這種mixin組合。編譯器能夠類型檢查全部的使用,並在自動完成列表中建議可用的成員:
clipboard.png

與類繼承進行對比,有個區別:一個類只能有一個基類。繼承多個基類在 JS 中不行的,所以在 TypeScript中也不行。


原文:
https://mariusschulz.com/blog...
https://mariusschulz.com/blog...

代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug


交流

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

https://github.com/qq44924588...

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png

相關文章
相關標籤/搜索