TypeScript 進行型別推斷的兩個關鍵概念:
- 聯集(Unions):將資料的允許型別擴充為兩種或多種可能的型別。
- 窄化(Narrowing):將資料的允許型別盡量減少。
聯集型別
例如以下程式:
let mathematician = Math.random() > 0.5 ? undefined : "HI";
mathematician 會是什麼型別?會是 string | undefined 聯集(Union)型別。
TypeScript 使用 | (管道) 運算符號,來表示資料可能的型別。
宣告聯集型別
以下範例,thinker 的初始資料為 null,但它的聯集型別為 string | null,所以後續也可以將字串資料,指派給 thinker 變數:
let thinker: string | null = null;
thinker = "Hi"; // 這是允許的
聯集屬性
例如以下程式:
let physicist = Math.random() > 0.5 ? "Hi" : 84; // 可得知 physicist 的聯集型別為 number | string。
physicist.toString(); // 這是允許的,因為 number 及 string 型別都有 toString() 函式。
physicist.toUpperCase(); // 這會報錯,因為 number 型別沒有 toUpperCase() 函式。
physicist.toFixed(); // 這會報錯,因為 string 型別沒有 toFixed() 函式。
限制對所有聯集型別存取不存在的屬性,是一種安全措施。
要使用僅存在於特定型別上的屬性時,我們需要向 TypeScript 提出更具體說明,這稱為窄化(narrowing)的過程。
窄化
窄化是指程式碼中,推斷出資料的型別,以及它的定義、宣告,使之更具體化。一旦 TypeScript 知道一個資料的型別比先前知道的更窄小,將允許我們將變數視為更具體的型別。可用於縮小型別的邏輯檢查,稱之為型別防護(type guard)。
指派的窄化
如果直接為變數指派資料,TypeScript 會將變數的型別窄化到該資料的型別。例:
let admiral: number | string; // 聯集型別
admiral = "Hi"; // 窄化為 string 型別
admiral.toUpperCase(); // 是可以的
admiral.toFixed(); // 會報錯,因為型別 string 沒有 toFixed() 函式。
或者也可能是以下,宣告型為為 number | string,然後馬上指派資料,也會發生窄化的效果:
let inventor: number | string = "Hi"; // 窄化為 string
inventor.toUpperCase(); // 正確:型別為 string
inventor.toFixed(); // 報錯,型別 string 沒有 toFixed() 函式。
條件檢查的窄化
例如以下範例:
let scientist = Math.random() > 0.5 ? "Hi" : 51; // number | string
if(scientist === "Hi"){
scientist.toUpperCase(); // 是可以的,因為前面 if 條件判斷的關係,所以 scientist 型別窄化為 string。
}
scientist.toUpperCase(); // 報錯,因為這裡的 scientist 型別為 number | string,在 number 型別中,沒有 toUpperCase() 函式。
使用條件邏輯窄化 TypeScript 的型別檢查,TypeScript 迫使我們安全地使用程式碼。
型別檢查的窄化
可以使用 typeof 關鍵字來窄化變數型別,例如:
let researcher = Math.random() > 0.5 ? "Hi" : 51; // number | string
if(typeof researcher === "string"){
researcher.toUpperCase(); // 是可以的,型別為 string
}
// 邏輯的否定 ! 以及 else 語法也可以一併使用
if(!(typeof researcher === "string")){
researcher.toFixed(); // 是可以的,型別為 number
}else{
researcher.toUpperCase(); // 是可以的,型別為 string
}
// 三元運算子範例
typeof researcher === "string" ? researcher.toUpperCase() : researcher.toFixed(); // 這也是可以的
字面型別(literal types)
前面已瞭解了聯集型別和窄化的處理,接下來可透過字面型別(literal types):用以表達更具體的原始型別版本。
例如以下這個範例:
const philosopher = "Hi";
乍看之下,沒錯,philosopher 的型別是 string。但也可以換另一種更具體的說法,可以說是 “Hi” 這個字面型別。
也就是如果將一個變數宣告為 const,並直接給一個固定資料,TypeScript 就會將該變數推斷為字面資料,視其為一種型別。例如下圖:

若是改用 let 來做變數的宣告,則會推斷為原始型別,如下例:

聯集型別註記
例如以下範例:
let lifespan: number | "ongoing" | "uncertain"; // 聯集型別 number | "ongoing" | "uncertain"
lifespan = 89; // 是可以的
lifespan = "ongoing"; // 是可以的
lifespan = true; // 這會報錯,因為 true 不可指派給型別 number | "ongoing" | "uncertain"
字面指派性
例如以下範例:
let specificallyAda: "Ada"; // 被宣告為字面型別「Ada」
specificallyAda = "Ada"; // 是可以的
specificallyAda = "Byron"; // 會報錯,型別 "Byron" 不可指派給型別 "Ada"。
嚴格的 null 檢查(strictNullChecks)
有一些程式語言,沒有嚴格檢查空值(null)的情況,會出現下面這樣將 null 指派給 string 的程式碼:
const firstName: string = null;
這種情況是非常不好的。
所以在 TypeScript 程式的設定檔當中,其中有一個選項是 strictNullChecks,該功能最好是要開啟(設定成 true),如果關閉的話,會讓 null 及 undefined 允許指派給變數,這非常不好。
真值(truthy)的窄化
JavaScript 中的所有值都是真值(truthy);有一些例外會被定義為假值(falsy),例:false、0、-0、0n、””、null、undefined 和 NaN。
TypeScript 還可以透過真假值檢查來窄化變數的型別,例如以下程式:
let geneticist = Math.random() > 0.5 ? "Hi" : undefined;
if(geneticist){
geneticist.toUpperCase(); // 這是允許的,因為窄化了真值
}
geneticist.toUpperCase(); // 這會報錯,因為 geneticist 可能是 undefined。
執行真假值檢查在邏輯運算符也是可以運作的,例:
geneticist && geneticist.toUpperCase(); // OK, string | undefined
geneticist?.toUpperCase(); // OK, string | undefined
沒有初始值的變數
沒有初始值的變數宣告,在 JavaScript 中,預設為 undefined。
在型別系統中提出了一個情況:如果將變數宣告為不包含 undefined 的型別,然後在指派資料之前嚐試使用的話,就會報出錯誤訊息:
let a: string;
a?.length; // 報錯:變數 a 在指派之前使用
a = "Hi";
a.length; // OK
但如果變數的型別包含 undefined 的話,那就不會報錯了,例:
let a: string | undefined;
a?.length; // OK
a = "Hi";
a.length; // OK
型別別名
以下程式為範例:
let rawDataFirst: boolean | number | string | null | undefined;
let rawDataSecond: boolean | number | string | null | undefined;
let rawDataThird: boolean | number | string | null | undefined;
TypeScript 允許型別別名(type aliases),變為更簡單的名稱。
型別別名以關鍵字 type、型別名稱、等號作為開頭,習慣上,型別別名以大寫駝峰式來寫,例:
type MyName = …;
上述程式,可改寫成:
type RawData = boolean | number | string | null | undefined;
let rawDataFirst: RawData;
let rawDataSecond: RawData;
let rawDataThird: RawData;
變得更易讀了。
JavaScript 沒有型別別名
也就是上述程式碼,若編譯為 JavaScript 時,會是如下:
let rawDataFirst;
let rawDataSecond;
let rawDataThird;
因為在 JavaScript 程式語法當中,是沒有型別別名這東西的,所以型別別名只會存在於 TypeScript 程式語法當中。
型別別名的組合
型別別名可以參考其它型別別名,例:
type Id = number | string;
type IdMaybe = Id | undefined | null; // 相當於 number | string | undefined | null
型別別名可以不用依照使用順序來宣告,所以也可以寫成如下:
type IdMaybe = Id | undefined | null;
type Id = number | string;
發佈留言
很抱歉,必須登入網站才能發佈留言。