利用交集 (&) 篩選合法的 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

好處

  1. 高度擴展性
    任何第三方或你自己都可以用 TypeScript 的「宣告合併」(declaration merging) 擴充,不需動到原本的 library 程式碼。例如:

    declare module 'zustand' {
      interface StoreMutators<S, A> {
        logger: StoreApi<S>
      }
    }
  2. 型別安全
    只有你註冊過的 mutator 名稱(key)才能被用來查型別,否則型別推斷會失敗,TypeScript 會報錯,防止拼錯或用錯 middleware 名稱。

  3. 自動型別推斷與遞迴
    可以依序自動套用多個 mutator,型別會自動正確推斷,維護和組合都很方便。

  4. 維護簡單
    新增或移除 mutator 只要調整 tuple 順序或內容,型別自動跟著變,不需手動調整多處型別。

其他方式

1. 用聯合型別或 enum 來限制 key

  • 缺點:擴展性差,第三方無法動態擴充,只能在原始碼裡加 key。

2. 用物件型別(Record)而非 interface

  • 缺點:無法用 declaration merging 擴充,型別不易被第三方 middleware 擴展。

3. 不做型別檢查,直接用 any

  • 缺點:完全失去型別安全,容易出錯。

4. 用函數重載(overload)或泛型參數

  • 缺點:型別組合複雜度高,維護困難,且不如 interface merging 靈活。

這種設計是 TypeScript 生態圈裡最推薦、最彈性、最型別安全的擴展方式,特別適合 plugin/middleware 生態系統。如果你要讓第三方能無痛擴充型別,這是最佳實踐。