想必大家在開發 TypeScript 的過程中,多少都會遇到一個情況:如果有一個 interface 其中之一的屬性是聯集型別,例如 string | number
。如果將該 interface 指定給一個物件,且要使用到該屬性時,就會發現只能使用 string
和 number
都共有的屬性。
為了解決這個問題,在 TypeScript 4.9 版的更新中,便引入了 satisfies
來解決型別推導不精確的問題。但既然有了斷言和 Type Guard,又為何需要 satisfies
來解決問題呢?
接下來,就讓我們來看看 satisfies
究竟解決什麼問題,又該如何使用吧!
satisfies
?satisfies
是為了解決在聯合型別或擴展型別的情況下,在不修改變數所屬型別的情況下,靜態推導型別的問題。
satisfies
的使用場景,主要集中在以下兩個方面:
satisfies
的使用方式satisfies
的使用方式,其實主要的目的和 interface
與 type
的效果接近。
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
。
除了聯集型別可以推定,也可以推定擴展型別,如 [key: string]: any;
TypeScript 僅會提示顯式聲明型別的屬性,但屬於擴展型別範圍的則不會顯示。例如顯式指定了 a
、b
兩個屬性。
type Obj = {
a: number,
b: string,
[key: string]: any,
}
const objType: Obj = {
a: 1,
b: 2, // 顯示型別錯誤
c: 3, // 不會自動推導
}
因為 c
並沒有在 Obj1
中被指定,則在選則 c
屬性時,編譯器並不會有任何提示。可以發現下面的自動補齊完全沒有出現 c
。
satisfies
後的差異相反地,使用 satisfies
後,則可以在顯示聲明時檢查顯式聲明 a
、b
,而自動推導 c
的型別。
type Obj = {
a: number,
b: string,
[key: string]: any,
}
const ObjSatisfies = {
a: 1,
b: 2, // 顯示型別錯誤
c: 3, // 自動推導為 number
}
可以發現右邊的 obj1.
出現了 c
的自動補齊,代表編譯器有自動推導出 c
的屬性與型別,且 c 的屬性也被正確推導為 number
,因而能夠使用 number
特有的屬性。
因此善用 satisfies
,可以讓我們在動態推導型別時,能夠兼具程式碼的簡潔,同時也能維護程式碼的型別安全,並讓自動補齊能夠順利做動。
透過這些例子,我們其實可以發現 TypeScript 其實擁有多種方式可以提升型別安全,但在個別的情境下,又有各自合適的案例。因此當我們需要為一個 type
或 interface
檢驗其屬性的型別,屬於聯集型別的哪一種時,就可以使用 satisfies
來達成目的。
如果有任何的筆誤或想法,都歡迎你留言一起討論,讓我們一起深入探索 TypeScript 的強大功能吧!