TypeScript Utility Types: Pick, Omit Với Partial Xài Sao Cho Đúng
API nào cũng cần một hình dạng hơi khác của cùng một dữ liệu: User đầy đủ, User an toàn để public (bỏ password hash), một form chỉ update đúng 2 field. Copy-paste interface cho từng trường hợp là kiểu gì cũng lệch nhau ngay khi có chỗ update. Utility type có sẵn của TypeScript giải quyết chuyện này bằng cách dẫn xuất type mới từ một nguồn sự thật duy nhất.
Vấn đề
interface User {
id: string
name: string
email: string
passwordHash: string
createdAt: Date
}
// Bị trùng lặp, theo thời gian sẽ lệch dần so với User
interface PublicUser {
id: string
name: string
email: string
createdAt: Date
}
Mỗi lần User đổi là phải nhớ update PublicUser theo. Mà thường là quên.
Partial<T>: Biến Mọi Field Thành Optional
Hợp nhất cho payload update, khi caller chỉ gửi những field nào thay đổi thôi.
function updateUser(id: string, changes: Partial<User>) {
// changes có thể là { name: 'New Name' } hoặc { email: '...' }
return db.users.update(id, changes)
}
Pick<T, K>: Chọn Ra Một Tập Con Field
Xài khi chỉ cần vài field từ type lớn hơn - card preview, option dropdown.
type UserPreview = Pick<User, 'id' | 'name'>
// { id: string; name: string }
Omit<T, K>: Loại Bỏ Field Cụ Thể
Ngược lại với Pick - giữ mọi thứ trừ mấy cái mình liệt kê ra. Hợp để lọc field nhạy cảm trước khi trả response.
type PublicUser = Omit<User, 'passwordHash'>
// { id: string; name: string; email: string; createdAt: Date }
Giờ PublicUser tự động bám theo User luôn. Thêm field vào User là nó tự xuất hiện trong PublicUser, khỏi đụng vào dòng này.
Kết Hợp Chúng Lại
Utility type ghép được với nhau. Một "form đổi tên" chỉ cho sửa mỗi tên:
type RenameForm = Partial<Pick<User, 'name'>>
// { name?: string }
Mấy Type Này Dẫn Xuất Từ Một Nguồn Ra Sao
Khi Nào Xài Cái Nào
- Partial<T>: payload PATCH/update, object config optional, form state trước khi submit.
- Pick<T, K>: preview, option dropdown, chỗ nào cần tập con nhỏ gọn, tường minh.
- Omit<T, K>: lọc field nhạy cảm hay nội bộ (password, ID nội bộ) trong khi vẫn tự động đồng bộ mọi thứ còn lại.
Mấy Cái Bẫy Hay Gặp
- Omit với key đã đổi tên/xóa vẫn im lặng chạy qua - TypeScript không báo lỗi nếu mình omit một key đã bị xóa khỏi type gốc rồi, nên gõ sai key trong danh sách dễ bị bỏ sót lắm. Nhớ soát lại từng key so với type nguồn.
- Partial che mất field thật sự bắt buộc - hàm update dùng
Partial<User>không phân biệt được "caller quênid" với "caller không muốn đổiid". Giữ định danh bắt buộc (nhưid) ở ngoàiPartial:{ id: string } & Partial<Omit<User, 'id'>>. - Xài
Record<string, unknown>thay vìPick/Omitlà bỏ luôn type safety - utility type giữ được liên kết với hình dạng gốc, cònRecordlỏng lẻo thì không.
Dẫn xuất, đừng lặp lại. Nếu hai type mô tả cùng một entity, một trong hai nên được build từ cái kia bằng
Pick,Omit, hoặcPartial- đừng maintain tay.

