[TypeScript] 快速上手 satisfies,讓你輕鬆推論型別與類型檢查

2024/04/08閱讀時間約 7 分鐘
TypeScript - satisfies

TypeScript - satisfies


想必大家在開發 TypeScript 的過程中,多少都會遇到一個情況:如果有一個 interface 其中之一的屬性是聯集型別,例如 string | number。如果將該 interface 指定給一個物件,且要使用到該屬性時,就會發現只能使用 stringnumber 都共有的屬性。

為了解決這個問題,在 TypeScript 4.9 版的更新中,便引入了 satisfies 來解決型別推導不精確的問題。但既然有了斷言和 Type Guard,又為何需要 satisfies 來解決問題呢?

接下來,就讓我們來看看 satisfies 究竟解決什麼問題,又該如何使用吧!




什麼時候需要用到 satisfies

satisfies 是為了解決在聯合型別或擴展型別的情況下,在不修改變數所屬型別的情況下,靜態推導型別的問題。

satisfies 的使用場景主要集中在以下兩個方面:

  • 不修改原有型別的情況下,確保資料符合特定型別。
  • 進行更細部的靜態型別推導,同時避免引入執行時的負擔。


satisfies 的使用方式

satisfies 的使用方式,其實主要的目的和 interfacetype 的效果接近。

interface Obj1 {
a: number;
b: string;
}

const obj1 = {
a: 1,
b: 2, // Type "number" is not assignable to type "string".
} satisfies Obj1;

接下來,就讓我們透過實際案例,來進一步了解 satisfies 吧!




一、為聯集屬性推定型別

假設有兩種 Task 的形式,一種是 DetailedTask,另一種則是 SimpleTask。當我們生成一個新的 User 物件時,task 的屬性有可能是一個物件或 string。但 TypeScript 沒辦法確認資料有可能是物件還是 string,因此在使用 string 專屬的 charAt 方法時就會報錯。

type SimpleTask = string;

interface DetailedTask {
description: string;
dueDate: Date;
}

type Task = SimpleTask | DetailedTask;

interface User {
name: string;
task: Task;
}

const badUser: User = {
name: "John",
task: "Finish report"
}

badUser.task.charAt() // charAt 會報錯


方案一:透過斷言 as 指定型別

直接透過 as 斷言來確認 badUser 的型別,但帶來的缺點也顯而易見。除了程式碼的易讀性會變低,也會讓程式碼脫離 TypeScript 的型別檢查。因為 as 的目的就是告訴編譯器,不要管我在做什麼,我告訴你這是什麼。

(badUser.task as SimpleTask).charAt(0); // OK


方案二:透過型別謂詞縮減型別

透過型別謂詞來確定型別。透過判別是否為 SimpleTask,讓 TS 知道如何辨別所屬的物件,進而提供可選用的方法。若想進一步了解型別謂詞,可參考 10 分鐘學會 TS 中必會的 5 種型別防禦 Type Guard

function isSimpleTask(task: Task): task is SimpleTask {
return typeof task === "string";
}

if (isSimpleTask(badUser.task)) {
badUser.task.charAt(0); // ok
}


方案三:透過 satisfies 自動推導型別

最後,便是使用 satisfies 來自動推導型別。相較於上面兩個方法,satisfies 能夠自動推導出 badUser.task 屬於 string 型別,因此能夠自動提供 charAt() 的方法。

const goodUser = {
name: "Cat",
task: "Finish the report",
} satisfies User;

goodUser.task.charAt(); // ok

與此同時,當我們滑到 goodUser.task 上時,可以看到編譯器已經幫我們推導出 string 的型別,讓我們能夠自然而然的使用 string 的方法 trim

satisfies type inference

satisfies type inference




二、動態推定擴展型別的資料型別

除了聯集型別可以推定,也可以推定擴展型別,如 [key: string]: any;

TypeScript 僅會提示顯式聲明型別的屬性,但屬於擴展型別範圍的則不會顯示。例如顯式指定了 ab 兩個屬性。

type Obj = {
a: number,
b: string,
[key: string]: any,
}

const objType: Obj = {
a: 1,
b: 2, // 顯示型別錯誤
c: 3, // 不會自動推導
}
raw-image



因為 c 並沒有在 Obj1 中被指定,則在選則 c 屬性時,編譯器並不會有任何提示。可以發現下面的自動補齊完全沒有出現 c



使用 satisfies 後的差異

相反地,使用 satisfies 後,則可以在顯示聲明時檢查顯式聲明 ab,而自動推導 c 的型別。

type Obj = {
a: number,
b: string,
[key: string]: any,
}

const ObjSatisfies = {
a: 1,
b: 2, // 顯示型別錯誤
c: 3, // 自動推導為 number
}
raw-image


可以發現右邊的 obj1. 出現了 c 的自動補齊,代表編譯器有自動推導出 c 的屬性與型別,且 c 的屬性也被正確推導為 number,因而能夠使用 number 特有的屬性。



因此善用 satisfies,可以讓我們在動態推導型別時,能夠兼具程式碼的簡潔,同時也能維護程式碼的型別安全,並讓自動補齊能夠順利做動。


結論與延伸

透過這些例子,我們其實可以發現 TypeScript 其實擁有多種方式可以提升型別安全,但在個別的情境下,又有各自合適的案例。因此當我們需要為一個 typeinterface 檢驗其屬性的型別,屬於聯集型別的哪一種時,就可以使用 satisfies 來達成目的。

如果有任何的筆誤或想法,都歡迎你留言一起討論,讓我們一起深入探索 TypeScript 的強大功能吧!


參考資料 Reference

此處作為整理前端(Frontend)和相關的 HTML、CSS、JavaScript、React 等前端觀念與技巧,全部都會收錄在這個專題之中。同時也會將相關的技術與反思記錄在此,歡迎各位讀者互相交流。
留言0
查看全部
發表第一個留言支持創作者!