【TypeScript 演化史 -- 2】基於控制流的類型分析 和 只讀屬性

做者:Marius Schulz
譯者:前端小智
來源:Marius Schulz

阿里雲最近在作活動,新老用戶低至2折,真心以爲很划算了,能夠點擊本條內容或者連接進行參與
https://promotion.aliyun.com/...html


基於控制流的類型分析

TypeScript 官網總結了基於控制流的類型分析:前端

TypeScript 2.0 實現了對局部變量和參數的控制流類型分析。之前,對類型保護進行類型分析僅限於 if 語句和 ?: 條件表達式,而且不包括賦值和控制流結構的影響,例如 returnbreak 語句。
使用 TypeScript 2.0,類型檢查器會分析語句和表達式全部可能的控制流,在任何指定的位置對聲明爲聯合類型的局部變量或參數產生最可能的具體類型(縮小範圍的類型)。

這是一個很深奧的解釋。下面的示例演示了 TypeScript 如何理解賦值給局部變量的影響,以及如何相應地縮小該變量的類型:git

let command: string | string[];

command = "pwd";
command.toLowerCase(); // 這裏,command 的類型是 'string'

command = ["ls", "-la"];
command.join(" "); //  這裏,command 的類型是 'string[]'

注意,全部代碼都位於同一個做用域內。儘管如此,類型檢查器在任何給定位置都爲 command 變量使用最具體的類型github

  • 在分配了字符串 「pwd」 以後,command 變量就不多是字符串數組(聯合類型中唯一的其餘選項)。所以,TypeScript 將 command 做爲 string 類型的變量,並容許調用toLowerCase() 方法。
  • 在分配了字符串數組 ["ls", "-la"] 以後,command 變量再也不被視爲字符串,如今它是一個字符串數組,因此對 join 方法的也就能調用了。

一樣因爲進行了相同的控制流分析,所以如下函數在 TypeScript 2.0 也能夠正確進行了類型檢查:typescript

function composeCommand(command: string | string[]): string{
  if (typeof command === 'string') {
    return command;
  }

  return command.join(' ')
}

編譯器如今知道,若是 command 參數的類型是 string,那麼函數老是在 if 語句中提早返回。因爲提早的退出行爲,command 參數的類型在 if 語句以後被限制爲string[]。所以,對 join 方法的調用將正確地檢查類型。express

TypeScript 2.0 以前,編譯器沒法推斷出上面的語義。所以,沒有從 command 變量的聯合類型中刪除字符串類型,併產生如下編譯時錯誤:數組

Property 'join' does not exist on type 'string | string[]'.

嚴格的 Null 檢查

當與可空類型一塊兒使用時,基於控制流的類型分析尤爲有用,可空類型使用包括 nullundefined 在聯合類型中的表示。一般,在使用可空類型的變量以前,咱們須要檢查該變量是否具備非空值:微信

type Person = {
  firstName: string;
  lastName?: string | null | undefined;
};

function getFullName(person: Person): string {
  const { firstName, lastName } = person;

  // 在這裏,咱們檢查 `lastName` 屬性的 虛值(falsy), 
  // 包含 `null` 和 `undefined`(以及其它值,例如 `""`)

   //包含`null`和`undefined`(以及其餘值,例如「」)
  if (!lastName) {
    return firstName;
  }

  return `${firstName} ${lastName}`;
}

在此,Person 類型定義了一個不可爲空的 firstName 屬性和一個可爲空的 lastName 屬性。 若是咱們要返回全名,則須要檢查 lastNamenull 或者undefined ,以免將字符串 "null""undefined" 附加到名字上。ide

爲了清晰可見,我將 undefined 的類型添加到 lastName 屬性的聯合類型中,儘管這是多餘的作法。 在嚴格的 null 檢查模式下,undefined 的類型會自動添加到可選屬性的聯合類型中,所以咱們沒必要顯式將其寫出。函數

明確賦值分析

基於控制流的另外一個新特性是明確賦值分析。在嚴格的 null 檢查模式下,對類型不容許爲 undefined 的局部變量有明確賦值的分析:

let name: string;

// Error: 在賦值前使用了變量 「name」
console.log(name);

該規則的一個例外是類型包括 undefined 的局部變量

let name: string | undefined;
console.log(name); // No error

明確的賦值分析是另外一種針對可空性缺陷的保護措施。其思想是確保每一個不可空的局部變量在使用以前都已正確初始化。

只讀屬性

TypeScript 2.0 中,readonly 修飾符被添加到語言中。使用 readonly 標記的屬性只能在初始化期間或從同一個類的構造函數中分配,其餘狀況一概不容許。

來看一個例子。下面是一個簡單的 Point 類型,它聲明瞭兩個只讀屬性 xy

type Point = {
  readonly x: number;
  readonly y: number;
};

如今,咱們能夠建立一個表示原點 point(0, 0) 的對象:

const origin: Point = { x:0, y:0 };

因爲 xy 標記爲 readonly,所以咱們沒法更改這兩個屬性的值:

// 錯誤:賦值表達式的左側
// 不能是常量或只讀屬性
origin.x = 100;

一個更現實的例子

雖然上面的示例可能看起來有些作做(確實是這樣),可是請考慮下面這樣的函數:

function moveX(p: Point, offset: number): Point {
  p.x += offset;
  return p;
}

moveX 函數不能修改給定 px 屬性。由於 x 是隻讀的,若是嘗試這麼,TypeScript 編譯器會給出錯誤提示:

clipboard.png

相反,moveX 應該返回一個具備更新的屬性值的 point,它相似這樣的:

function moveX(p: Point, offset: number): Point {
  return {
    x: p.x + offset,
    y: p.y
  };
}

只讀類屬性

我們還能夠將 readonly 修飾符應用於類中聲明的屬性。以下所示,有一個 Circle 類,它有一個只讀 的radius 屬性和一個get area 屬性,後者是隱式只讀的,由於沒有 setter:

class Circle {
  readonly radius: number;

  constructor(radius: number) {
    this.radius = radius;
  }

  get area() {
    return Math.PI * this.radius ** 2;
  }
}

注意,使用 ES7 指數運算符對 radius 進行平方。radiusarea 屬性均可以從類外部讀取(由於它們都不是私有(private)的),可是不能寫入(由於它們都是隻讀(readonly)的):

const unitCircle = new Circle(1);
unitCircle.radius; // 1
unitCircle.area; // 3.141592653589793

// 錯誤:賦值表達式的左側
// 不能是常量或只讀屬性
unitCircle.radius = 42;

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
unitCircle.area = 42;

只讀索引簽名

此外,可使用 readonly 修飾符標記索引簽名。ReadonlyArray<T> 類型使用這樣的索引簽名來阻止對索引屬性的賦值:

interface ReadonlyArray<T> {
  readonly length: number;
  // ...
  readonly [n: number]: T;
}

因爲只讀索引簽名,編譯器將如下賦值標記爲無效

const primesBelow10: ReadonlyArray<number> = [2, 3, 5, 7];

// Error: 類型 「ReadonlyArray<number>」 中的索引簽名僅容許讀取
primesBelow10[4] = 11;

只讀與不變性

readonly 修飾符是TypeScript類型系統的一部分。它只被編譯器用來檢查非法的屬性分配。一旦TypeScript代碼被編譯成JavaScript,全部readonly的概念都消失了。您能夠隨意擺弄這個小示例,看看如何轉換隻讀屬性。

由於 readonly 只是一個編譯時工件,因此沒有針對運行時的屬性分配的保護。也就是說,它是類型系統的另外一個特性,經過讓編譯器從 TypeScript 代碼庫中檢查意外的屬性分配,幫助你編寫正確的代碼。

總結

基於控制流的類型分析是 TypeScript 類型系統的一個強大的補充。類型檢查器如今理解了控制流中賦值和跳轉的語義,從而大大減小了對類型保護的須要。能夠經過消除 nullundefined 類型來簡化可空變量的處理。最後,控制流分析防止引用在給定位置沒有明確分配的變量。

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

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

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

交流(歡迎加入羣,羣工做日都會發紅包,互動討論技術)

阿里雲最近在作活動,新老用戶低至2折,真心以爲很划算了,能夠點擊本條內容或者連接進行參與
https://promotion.aliyun.com/...

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

https://github.com/qq449245884/xiaozhi

由於篇幅的限制,今天的分享只到這裏。若是你們想了解更多的內容的話,能夠去掃一掃每篇文章最下面的二維碼,而後關注我們的微信公衆號,瞭解更多的資訊和有價值的內容。

clipboard.png

相關文章
相關標籤/搜索