TypeScript 工具型別 (Utility Types)
TypeScript 內建了許多全域的「工具型別 (Utility Types)」。你可以把它們想像成是「專門處理型別的函式」:你輸入一個型別,它幫你加工處理後,吐出一個新的型別。
掌握這些工具型別非常重要,因為它們可以幫助你:
- 減少重複定義:不需要為了微小的差異(例如一個欄位變可選)就重新寫一個介面。
- 保持型別的一致性:當原始型別修改時,衍生出來的型別會自動更新。
屬性修飾工具
這類工具主要用來修改介面中屬性的特性(例如是否選填、是否唯讀)。
Partial<T>
用途:將型別 T 的所有屬性都變成「可選的 (optional)」。
使用場景:最常見的就是在做「更新」操作時。當我們要更新一個使用者的資料,我們可能只想更新他的名字,而不需要連同 Email 一起傳送。
interface User {
id: number;
name: string;
email: string;
}
// 原始做法:手動重寫一個 interface
// interface PartialUser {
// id?: number;
// name?: string;
// email?: string;
// }
// 使用 Partial:自動產生上述結構
type PartialUser = Partial<User>;
function updateUser(id: number, updates: Partial<User>) {
// ...更新邏輯
}
// OK:只傳送 name
updateUser(1, { name: '小明' });
// OK:什麼都不傳 (因為所有屬性都變 optional 了)
updateUser(2, {});
Required<T>
用途:將型別 T 的所有屬性都變成「必填的 (required)」,也就是移除 ? 修飾符。
使用場景:通常用於處理設定檔。我們定義設定介面時,允許使用者只填部分設定(其他用預設值),但在程式內部邏輯中,我們希望能確保所有設定值都已經補齊了。
interface Config {
host?: string;
port?: number;
timeout?: number;
}
// 假設這是我們的預設設定
const defaultConfig = {
host: 'localhost',
port: 8080,
timeout: 5000,
};
// 經過合併處理後,我們確定回傳的設定一定包含所有欄位
function processConfig(userConfig: Config): Required<Config> {
return { ...defaultConfig, ...userConfig };
}
const finalConfig = processConfig({ port: 3000 });
// 這裡 finalConfig.host 一定是 string,不會是 undefined
console.log(finalConfig.host);
Readonly<T>
用途:將型別 T 的所有屬性都變成「唯讀 (readonly)」。
使用場景:當你希望確保某個物件在傳遞過程中不會被意外修改時(類似 React 的 props 或 state,或者 Redux 的 store)。
interface Todo {
title: string;
}
const todo: Readonly<Todo> = {
title: '寫程式',
};
// todo.title = '睡覺';
// 錯誤!無法指派給 'title',因為它是唯讀屬性
結構選取與排除工具
這類工具用來從現有的型別中「挑選」或「剔除」某些屬性,產生新的型別。
Pick<T, K>
用途:從型別 T 中,「挑選」出一組屬性 K 來建立新型別。
使用場景:當你要從一個巨大的介面中,只取出一小部分欄位來用時。例如:從完整的 User 物件中,只提取出用來顯示在列表上的欄位。
interface User {
id: number;
name: string;
email: string;
address: string;
phone: string;
// ... 還有很多欄位
}
// 我們只想在畫面上顯示名字和 Email
type UserPreview = Pick<User, 'name' | 'email'>;
// UserPreview 等同於:
// {
// name: string;
// email: string;
// }
Omit<T, K>
用途:這剛好跟 Pick 相反。從型別 T 中,「排除」掉一組屬性 K,保留剩下的。
使用場景:當你要移除某些敏感資訊(如密碼),或是在繼承介面時想移除某些不適用的欄位。
interface User {
id: number;
name: string;
password: string; // 敏感資訊
isAdmin: boolean; // 敏感資訊
}
// 傳給前端的資料,要過濾掉 password 和 isAdmin
type UserDto = Omit<User, 'password' | 'isAdmin'>;
// UserDto 等同於:
// {
// id: number;
// name: string;
// }
小技巧:選取欄位少時用
Pick,選取欄位多(只想扣掉一兩個)時用Omit。
物件映射工具
Record<K, T>
用途:建立一個物件型別,其所有的 Key 都是 K 型別,所有的 Value 都是 T 型別。
使用場景:非常適合用來定義「字典 (Dictionary)」或「映射表 (Map)」結構。
type Page = 'home' | 'about' | 'contact';
interface PageInfo {
title: string;
url: string;
}
// 我們希望定義一個物件,它的 key 只能是 'home' | 'about' | 'contact'
// 而 value 必須是 PageInfo
const nav: Record<Page, PageInfo> = {
home: { title: '首頁', url: '/' },
about: { title: '關於我們', url: '/about' },
contact: { title: '聯絡資訊', url: '/contact' },
// other: { ... } // 錯誤!'other' 不在 Page 定義的 key 之中
};
聯合型別 (Union) 處理工具
這類工具專門用來處理 type A = "a" | "b" | "c" 這種聯合型別。
Exclude<T, U>
用途:從聯合型別 T 中,「剔除」掉可以指派給 U 的部分。(簡單記法:T - U)。
type Status = 'success' | 'error' | 'loading';
// 我想要一個不包含 'loading' 的狀態
type FinishedStatus = Exclude<Status, 'loading'>;
// 結果:'success' | 'error'
Extract<T, U>
用途:從聯合型別 T 中,「提取」出可以指派給 U 的部分,也就是 T 和 U 的交集。
type Shape = 'circle' | 'square' | 'triangle';
type TwoDShape = 'circle' | 'square' | 'line';
// 取出兩邊都有的形狀
type CommonShape = Extract<Shape, TwoDShape>;
// 結果:'circle' | 'square'
NonNullable<T>
用途:從型別 T 中剔除 null 和 undefined。
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;
// 結果:string
函式相關工具
這類工具非常強大,常用於當你使用第三方套件,該套件沒有直接匯出某個型別,但你有那個函式時。
ReturnType<T>
用途:取得一個函式的回傳值型別。
使用場景:Redux 的 Action Creator,或者任何工廠函式。
function createUser() {
return {
id: 1,
name: '小明',
settings: { theme: 'dark', notifications: true },
};
}
// 假設我們想拿到 settings 的型別,但原本沒定義 interface
// 我們可以這樣做:
// 1. 先拿到函式回傳的整個物件型別
type UserObject = ReturnType<typeof createUser>;
// 2. 再從裡面抓出 settings
type UserSettings = UserObject['settings'];
Parameters<T>
用途:取得一個函式的參數型別(回傳的是一個 Tuple)。
function add(x: number, y: number): number {
return x + y;
}
type AddParams = Parameters<typeof add>;
// 結果:[x: number, y: number]
// 甚至可以抓出第一個參數的型別
type FirstArg = AddParams[0]; // number
字串操作工具 (TypeScript 4.1+)
這些是用來處理字串 Literal Types 的,通常搭配 Template Literal Types 使用。
Uppercase<S>: 轉大寫Lowercase<S>: 轉小寫Capitalize<S>: 首字大寫Uncapitalize<S>: 首字小寫
type Event = 'click' | 'change';
// 自動產生 'onClick' | 'onChange'
type HandlerName = `on${Capitalize<Event>}`;
總結
工具型別是 TypeScript 讓程式碼更簡潔、更 DRY 的利器。
- 要修改屬性?找
Partial,Required,Readonly。 - 要篩選欄位?找
Pick,Omit。 - 要定義對照表?找
Record。 - 要處理 Union?找
Exclude,Extract。 - 要從函式反推型別?找
ReturnType,Parameters。
熟練這些工具,你的 TypeScript 功力將會提升一個檔次!