利用交集 (&) 篩選合法的 key
// 定義一個 Mutators 介面,只有 'a' 和 'b' 是合法 key
interface Mutators {
a: string
b: number
}
// 所有合法的 mutator 名稱
type MutatorIdentifier = keyof Mutators // 'a' | 'b'
// 取出 Mutators 裡指定 key 的型別,但只允許合法 key
type GetMutator<M> = Mutators[M & MutatorIdentifier]
// 正確:'a' 是合法 key
type X = GetMutator<'a'> // string
// 正確:'b' 是合法 key
type Y = GetMutator<'b'> // number
// 錯誤:'c' 不是合法 key,結果是 never
type Z = GetMutator<'c'> // never好處
-
高度擴展性
任何第三方或你自己都可以用 TypeScript 的「宣告合併」(declaration merging) 擴充,不需動到原本的 library 程式碼。例如:declare module 'zustand' { interface StoreMutators<S, A> { logger: StoreApi<S> } } -
型別安全
只有你註冊過的 mutator 名稱(key)才能被用來查型別,否則型別推斷會失敗,TypeScript 會報錯,防止拼錯或用錯 middleware 名稱。 -
自動型別推斷與遞迴
可以依序自動套用多個 mutator,型別會自動正確推斷,維護和組合都很方便。 -
維護簡單
新增或移除 mutator 只要調整 tuple 順序或內容,型別自動跟著變,不需手動調整多處型別。
其他方式
1. 用聯合型別或 enum 來限制 key
- 缺點:擴展性差,第三方無法動態擴充,只能在原始碼裡加 key。
2. 用物件型別(Record)而非 interface
- 缺點:無法用 declaration merging 擴充,型別不易被第三方 middleware 擴展。
3. 不做型別檢查,直接用 any
- 缺點:完全失去型別安全,容易出錯。
4. 用函數重載(overload)或泛型參數
- 缺點:型別組合複雜度高,維護困難,且不如 interface merging 靈活。
這種設計是 TypeScript 生態圈裡最推薦、最彈性、最型別安全的擴展方式,特別適合 plugin/middleware 生態系統。如果你要讓第三方能無痛擴充型別,這是最佳實踐。