TypeScript ships a set of global utility types that transform existing types. They are implemented with mapped types and conditional types — understanding their internals is what separates someone who looks them up from someone who builds their own.
TypeScript cung cấp một tập hợp các utility type toàn cục giúp biến đổi các kiểu hiện có. Chúng được triển khai bằng mapped types và conditional types — hiểu cách hoạt động bên trong giúp bạn xây dựng các abstraction riêng thay vì chỉ tra cứu.
Object Manipulation: Partial, Required, Readonly, Record
Thao Tác Với Object: Partial, Required, Readonly, Record
These four utility types modify the requiredness and mutability of object type properties.
Bốn utility type này thay đổi tính bắt buộc và khả năng thay đổi của các properties trong một object type.
1interface User {2 id: number;3 name: string;4 email: string;5 role: "admin" | "user";6}78// Partial<T>: all properties become optional9type UserUpdate = Partial<User>;10// { id?: number; name?: string; email?: string; role?: "admin" | "user" }1112// Internally:1// Readonly<T>: all properties become readonly2const config: Readonly<User> = {3 id: 1, name: "Alice", email: "alice@example.com", role: "admin",4};5// config.name = "Bob"; // Error: Cannot assign to 'name' — read-only property67// For deep immutability build a recursive version:8type DeepReadonly<T> = {9 readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];10};1112// Record<K, V>: builds an object type with keys K and values VPick and Omit derive subtypes from object types. They are indispensable for building DTOs, API response types, and form schemas.
Pick và Omit cho phép bạn tạo subtypes từ các object types. Chúng đặc biệt hữu ích khi xây dựng DTOs, API response types và form schemas.
1interface Article {2 id: string;3 title: string;4 body: string;5 authorId: string;6 publishedAt: Date | null;7 tags: string[];8 viewCount: number;9}1011// Pick<T, K>: keep only the listed keys12type ArticleCard = Pick<Article, "id" | "title" | "publishedAt" | "tags">;Omit kém an toàn hơn Pick vì tham số thứ hai chấp nhận string thay vì chỉ keyof T. Bạn có thể tạo StrictOmit để khắc phục: type StrictOmit<T, K extends keyof T> = Omit<T, K>
These three utility types filter and extract union members based on conditions. All three are powered by conditional type distributivity — when a conditional type is applied to a union, it distributes over each member.
Ba utility type này hoạt động trên union types, lọc và trích xuất các thành viên của union dựa trên điều kiện. Tất cả đều dựa trên conditional type distributivity.
1// Exclude<T, U>: remove union members assignable to U2type Status = "active" | "inactive" | "pending" | "deleted";3type LiveStatus = Exclude<Status, "inactive" | "deleted">;4// "active" | "pending"56// Extract<T, U>: keep only union members assignable to U7type Primitive = string | number | boolean | null | undefined;8type Nullish = Extract<Primitive, null | undefined>;9// null | undefined1011// NonNullable<T>: remove null and undefined12type MaybeUser = User | null | undefined;13type DefiniteUser = NonNullable<MaybeUser>;14// User1516// Internals — distributive conditional types:17type MyExclude<T, U> = T extends U ? never : T;18type MyExtract<T, U> = T extends U ? T : never;19type MyNonNullable<T> = T extends null | undefined ? never : T;1// Practical: filter a discriminated union by tag2type AppEvent =3 | { type: "click"; x: number; y: number }4 | { type: "keydown"; key: string }5 | { type: "keyup"; key: string }6 | { type: "resize"; width: number; height: number };78// Extract all keyboard events9type KeyboardEvent = Extract<AppEvent, { type: "keydown" | "keyup" }>;10// { type: "keydown"; key: string } | { type: "keyup"; key: string }1112// Exclude events that carry a key property13type PointerEvent = Exclude<AppEvent, { key: string }>;14// { type: "click"; ... } | { type: "resize"; ... }Function Tools: ReturnType, Parameters, ConstructorParameters
Công Cụ Hàm: ReturnType, Parameters, ConstructorParameters
These utility types extract type information from function signatures — invaluable when working with third-party functions whose types you do not control.
Những utility type này cho phép bạn trích xuất thông tin kiểu từ function signatures — rất hữu ích khi làm việc với các hàm bên thứ ba mà bạn không kiểm soát.
1async function fetchPaginatedUsers(2 page: number,3 limit: number,4 filters?: { role?: string; active?: boolean }5): Promise<{ data: User[]; total: number }> {6 return { data: [], total: 0 };7}89// Infer the return type without writing it manually10type FetchResult = ReturnType<typeof fetchPaginatedUsers>;11// Promise<{ data: User[]; total: number }>121class HttpClient {2 constructor(3 private readonly baseUrl: string,4 private readonly apiKey: string,5 private readonly timeout = 5_0006 ) {}78 get<T>(path: string): Promise<T> {9 return fetch(`${this.baseUrl}${path}`).then((r) => r.json() as Promise<T>);10 }11}12Awaited<T> recursively unwraps Promise types, mirroring the actual behaviour of await — including multiple levels of nesting and custom thenables.
Awaited<T> đệ quy unwrap các Promise types, phản ánh hành vi thực của await — kể cả với nhiều cấp lồng nhau và các thenable tùy chỉnh.
1type A = Awaited<Promise<string>>; // string2type B = Awaited<Promise<Promise<number>>>; // number (deeply unwrapped)3type C = Awaited<string>; // string (non-thenable passthrough)45// Primary use case: derive the resolved type of an async function6async function loadDashboard() {7 const [user, stats, notifications] = await Promise.all([8 fetchUser(),9 fetchStats(),10 fetchNotifications(),11 ]);12 return { user, stats, notifications };The real power arrives when you combine mapped types and conditional types to build utility types tailored to your own domain.
Sức mạnh thực sự đến khi bạn kết hợp mapped types và conditional types để tạo ra utility types của riêng mình, phù hợp với domain cụ thể.
1// DeepPartial — recursively make all nested properties optional2type DeepPartial<T> = T extends object3 ? { [K in keyof T]?: DeepPartial<T[K]> }4 : T;56interface AppState {7 user: { id: string; profile: { name: string; avatar: string } };8 settings: { theme: "dark" | "light"; language: string };9}1011type PartialState = DeepPartial<AppState>;12// user.profile.avatar is now string | undefined1// Paths — generate all valid dot-notation paths of a nested object2type Paths<T, Prefix extends string = ""> = {3 [K in keyof T & string]:4 T[K] extends object5 ? Paths<T[K], `${Prefix}${K}.`> | `${Prefix}${K}`6 : `${Prefix}${K}`;7}[keyof T & string];89interface Config {10 database: { host: string; port: number };11 cache: { ttl: number; prefix: string };12}Khi xây dựng recursive utility types, luôn đặt base case rõ ràng để tránh lỗi 'Type instantiation is excessively deep'. Giới hạn đệ quy mặc định của TypeScript là khoảng 100 cấp.
Partial và Required toggle tính optional của properties; ký hiệu -? xóa modifier optional một cách tường minh
Readonly và Record kiểm soát tính bất biến và hình dạng object type
Pick giữ lại các keys được liệt kê; Omit loại bỏ chúng — dùng StrictOmit để an toàn hơn
Exclude và Extract lọc union members thông qua conditional type distributivity
ReturnType và Parameters cho phép làm việc với function signatures mà không cần viết lại chúng
Awaited đệ quy unwrap Promises — kết hợp với ReturnType để type-safe async code
PickByValue và Paths là ví dụ về custom utility types sử dụng key remapping và template literal types