寫專案時一定會遇到這種情況:
同一個「使用者」,在不同地方需要的欄位不太一樣:
如果每種情況都重寫一個 type,欄位一多、一改就會改到漏掉。所以 TS 內建了一組工具型別,讓你從一個「主型別」直接生出其他型別,今天就來聊聊關於工具型別 Utility Types。
以下範例都用這個 User:
type User = {
id: number;
name: string;
email: string;
password: string;
age?: number;
};
Partial<User> 會把 User 的每個欄位都加上 ?。最典型的用法就是「修改資料的表單 / PATCH API」:使用者只改了名字,就只送名字。
function updateUser(id: number, data: Partial<User>) {
console.log(`更新使用者 ${id}`, data);
}
updateUser(1, { name: '小華' }); // ✅ 只改名字
updateUser(1, { age: 19, email: 'b@birc.tw' }); // ✅ 改兩個
updateUser(1, { age: '十九' });
// ❌ Type 'string' is not assignable to type 'number'.
欄位都變成可選了,但型別檢查跟拼字檢查都還在,這就是比直接寫 any 好的地方。
Required 跟 Partial 相反,把所有 ? 拿掉。例如註冊流程走完之後,age 一定要有:
type CompleteUser = Required<User>;
const cu: CompleteUser = { id: 1, name: '小明', email: 'a@birc.tw', password: '1234' };
// ❌ Property 'age' is missing in type '{ id: number; name: string; email: string; password: string; }' but required in type 'Required<User>'.
Pick<User, 'email' | 'password'> 會從 User 裡挑出指定的欄位,要挑的欄位用第四章的 Literal + Union 來寫。適合「登入表單」這種只需填寫一小部分欄位的情況:
type LoginForm = Pick<User, 'email' | 'password'>;
// 等同於 { email: string; password: string }
const form: LoginForm = { email: 'a@birc.tw', password: '1234' }; // ✅
const form2: LoginForm = { email: 'a@birc.tw' };
// ❌ Property 'password' is missing in type '{ email: string; }' but required in type 'LoginForm'.
Omit 剛好跟 Pick 相反:全部留下,除了指定的欄位。最重要的用法就是「公開資料不能有密碼」:
type PublicUser = Omit<User, 'password'>;
// 等同於 { id: number; name: string; email: string; age?: number }
function toPublic(user: User): PublicUser {
const { password, ...rest } = user; // 把 password 拆出來丟掉
return rest;
}
const pu = toPublic({ id: 1, name: '小明', email: 'a@birc.tw', password: '1234' });
console.log(pu.password);
// ❌ Property 'password' does not exist on type 'PublicUser'.
如果 API 回傳的型別寫成 PublicUser,就算哪天有人不小心想把密碼塞進去,TS 也會擋下來:
const leak: PublicUser = { id: 1, name: '小明', email: 'a@birc.tw', password: '1234' };
// ❌ Object literal may only specify known properties, and 'password' does not exist in type 'PublicUser'.
Readonly<User> 就是幫每個欄位都加上本章一開始講的 readonly。適合「從 API 拿到、只拿來顯示、不該被改」的資料:
const me: Readonly<User> = { id: 1, name: '小明', email: 'a@birc.tw', password: '1234' };
me.name = '小華';
// ❌ Cannot assign to 'name' because it is a read-only property.
工具型別可以疊在一起。例如「修改資料時,id 不能改、其他欄位都是選填」:
type UserUpdate = Partial<Omit<User, 'id'>>;
// 先 Omit 掉 id,再全部變成可選
const patch: UserUpdate = { name: '小華' }; // ✅
const bad: UserUpdate = { id: 2 };
// ❌ Object literal may only specify known properties, and 'id' does not exist in type 'Partial<Omit<User, "id">>'.