TypeScript 工具型別 (Utility Types)
TypeScript 提供了許多內建的工具型別 (Utility Types),用來進行常見的型別轉換。這些工具型別讓我們可以更方便地操作和轉換型別。
屬性修飾工具型別
Partial<T>
將所有屬性變成可選:
interface User {
id: number;
name: string;
email: string;
}
type PartialUser = Partial<User>;
// 等同於:
// {
// id?: number;
// name?: string;
// email?: string;
// }
function updateUser(id: number, updates: Partial<User>): void {
// 可以只更新部分屬性
}
updateUser(1, { name: '小明' });
updateUser(2, { email: 'test@example.com' });
Required<T>
將所有屬性變成必要:
interface Config {
host?: string;
port?: number;
ssl?: boolean;
}
type RequiredConfig = Required<Config>;
// 等同於:
// {
// host: string;
// port: number;
// ssl: boolean;
// }
const config: RequiredConfig = {
host: 'localhost',
port: 3000,
ssl: true,
};
Readonly<T>
將所有屬性變成唯讀:
interface Point {
x: number;
y: number;
}
type ReadonlyPoint = Readonly<Point>;
// 等同於:
// {
// readonly x: number;
// readonly y: number;
// }
const origin: ReadonlyPoint = { x: 0, y: 0 };
// origin.x = 10; // 錯誤:無法指派給 'x',因為它是唯讀屬性
選取與排除工具型別
Pick<T, K>
從 T 中選取指定的屬性:
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
type UserPreview = Pick<User, 'id' | 'name' | 'email'>;
// 等同於:
// {
// id: number;
// name: string;
// email: string;
// }
function getUserPreview(user: User): UserPreview {
return {
id: user.id,
name: user.name,
email: user.email,
};
}
Omit<T, K>
從 T 中排除指定的屬性:
interface User {
id: number;
name: string;
email: string;
password: string;
}
type UserWithoutPassword = Omit<User, 'password'>;
// 等同於:
// {
// id: number;
// name: string;
// email: string;
// }
type PublicUser = Omit<User, 'password' | 'email'>;
// {
// id: number;
// name: string;
// }
Record 型別
Record<K, T>
建立一個物件型別,其鍵為 K,值為 T:
type Role = 'admin' | 'user' | 'guest';
interface Permission {
read: boolean;
write: boolean;
delete: boolean;
}
type RolePermissions = Record<Role, Permission>;
const permissions: RolePermissions = {
admin: { read: true, write: true, delete: true },
user: { read: true, write: true, delete: false },
guest: { read: true, write: false, delete: false },
};
// 動態鍵
type StringMap = Record<string, string>;
const translations: StringMap = {
hello: '你好',
goodbye: '再見',
thanks: '謝謝',
};
聯合型別工具
Exclude<T, U>
從聯合型別 T 中排除 U:
type AllColors = 'red' | 'green' | 'blue' | 'yellow';
type PrimaryColors = Exclude<AllColors, 'yellow'>;
// "red" | "green" | "blue"
type Numbers = 1 | 2 | 3 | 4 | 5;
type OddNumbers = Exclude<Numbers, 2 | 4>;
// 1 | 3 | 5
Extract<T, U>
從聯合型別 T 中提取 U:
type AllTypes = string | number | boolean | null | undefined;
type Primitives = Extract<AllTypes, string | number | boolean>;
// string | number | boolean
type Event = 'click' | 'focus' | 'blur' | 'change';
type FocusEvents = Extract<Event, 'focus' | 'blur'>;
// "focus" | "blur"
NonNullable<T>
從 T 中排除 null 和 undefined:
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;
// string
type Value = number | null | undefined;
type NonNullValue = NonNullable<Value>;
// number
函式相關工具型別
ReturnType<T>
取得函式的回傳型別:
function createUser() {
return { id: 1, name: '小明', email: 'ming@example.com' };
}
type User = ReturnType<typeof createUser>;
// { id: number; name: string; email: string }
// 搭配泛型函式
type NumberArrayReturn = ReturnType<() => number[]>;
// number[]
Parameters<T>
取得函式的參數型別(元組):
function greet(name: string, age: number): string {
return `${name} is ${age} years old`;
}
type GreetParams = Parameters<typeof greet>;
// [name: string, age: number]
// 取得個別參數
type FirstParam = Parameters<typeof greet>[0]; // string
type SecondParam = Parameters<typeof greet>[1]; // number
ConstructorParameters<T>
取得類別建構子的參數型別:
class User {
constructor(
public name: string,
public age: number
) {}
}
type UserConstructorParams = ConstructorParameters<typeof User>;
// [name: string, age: number]
InstanceType<T>
取得類別的實例型別:
class User {
constructor(public name: string) {}
}
type UserInstance = InstanceType<typeof User>;
// User
function createInstance<T extends new (...args: any[]) => any>(
Class: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
return new Class(...args);
}
字串操作型別
TypeScript 4.1+ 提供內建的字串操作型別:
Uppercase<S>
type Greeting = 'hello';
type LoudGreeting = Uppercase<Greeting>; // "HELLO"
Lowercase<S>
type Shout = 'HELLO';
type Whisper = Lowercase<Shout>; // "hello"
Capitalize<S>
type Word = 'hello';
type Title = Capitalize<Word>; // "Hello"
Uncapitalize<S>
type Title = 'Hello';
type Word = Uncapitalize<Title>; // "hello"
實用範例
建立表單型別
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
// 新增使用者的表單(不需要 id 和 createdAt)
type CreateUserForm = Omit<User, 'id' | 'createdAt'>;
// 更新使用者的表單(所有欄位可選,但不包含 id)
type UpdateUserForm = Partial<Omit<User, 'id'>>;
// API 回應(全部唯讀)
type UserResponse = Readonly<User>;
API 資源型別
interface Resource {
id: number;
createdAt: Date;
updatedAt: Date;
}
type CreateDTO<T extends Resource> = Omit<T, keyof Resource>;
type UpdateDTO<T extends Resource> = Partial<CreateDTO<T>>;
interface Post extends Resource {
title: string;
content: string;
authorId: number;
}
type CreatePost = CreateDTO<Post>;
// { title: string; content: string; authorId: number }
type UpdatePost = UpdateDTO<Post>;
// { title?: string; content?: string; authorId?: number }
狀態管理
interface AppState {
user: User | null;
posts: Post[];
isLoading: boolean;
error: string | null;
}
// Reducer action 型別
type SetAction<K extends keyof AppState> = {
type: `SET_${Uppercase<K & string>}`;
payload: AppState[K];
};
// 產生所有可能的 action
type AppActions = {
[K in keyof AppState]: SetAction<K>;
}[keyof AppState];
事件處理器型別
type EventMap = {
click: MouseEvent;
keydown: KeyboardEvent;
focus: FocusEvent;
};
type EventHandler<K extends keyof EventMap> = (event: EventMap[K]) => void;
function on<K extends keyof EventMap>(event: K, handler: EventHandler<K>): void {
// ...
}
on('click', (e) => {
console.log(e.clientX); // e 是 MouseEvent
});
on('keydown', (e) => {
console.log(e.key); // e 是 KeyboardEvent
});
組合工具型別
// 深度 Readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
// 深度 Partial
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
// 使用
interface NestedConfig {
server: {
host: string;
port: number;
};
database: {
url: string;
pool: {
min: number;
max: number;
};
};
}
type PartialConfig = DeepPartial<NestedConfig>;
// 所有巢狀屬性都變成可選