Utility type là các kiểu generic có sẵn trong TypeScript, dùng để biến một kiểu thành kiểu khác. Thay vì viết một kiểu User thứ hai với mọi trường đều tùy chọn, bạn viết Partial<User>; thay vì sao chép ba trường, bạn viết Pick<User, "id" | "name">. Chúng là kiểu toàn cục nên không cần import.
Khi User thay đổi, cả bốn kiểu dẫn xuất đều thay đổi theo. Thư viện chuẩn của TypeScript (lib.es5.d.ts) khai báo 22 utility type. Các phần bên dưới liệt kê tất cả, xếp nhóm theo loại kiểu mà chúng xử lý, kèm liên kết đến trang chi tiết nếu có.
Kiểu đối tượng: Partial, Required, Readonly, Pick, Omit, Record
| Utility type | Tác dụng | Ví dụ |
|---|---|---|
Partial<T> | biến mọi thuộc tính thành tùy chọn | Partial<User> cho dữ liệu cập nhật |
Required<T> | biến mọi thuộc tính thành bắt buộc (bỏ ?) | Required<Config> sau khi đã áp giá trị mặc định |
Readonly<T> | biến mọi thuộc tính thành readonly | Readonly<State> |
Pick<T, K> | chỉ giữ lại các khóa K | Pick<User, "id" | "name"> |
Omit<T, K> | bỏ đi các khóa K | Omit<User, "password"> |
Record<K, V> | một kiểu đối tượng có khóa K và giá trị V | Record<"en" | "de", string> |
Required<T> là kiểu ngược lại của Partial<T>; trang Partial nói về cả hai, kể cả giá trị undefined tường minh mà một phép spread như trên để lọt qua.
Union type: Exclude, Extract, NonNullable
| Utility type | Tác dụng | Ví dụ |
|---|---|---|
Exclude<U, M> | bỏ các thành viên union gán được cho M | Exclude<"a" | "b" | "c", "a"> là "b" | "c" |
Extract<U, M> | giữ các thành viên union gán được cho M | Extract<string | number, number> là number |
NonNullable<T> | bỏ null và undefined | NonNullable<string | null> là string |
Ba kiểu này làm việc trên union, không phải đối tượng. Đó là khác biệt then chốt so với Pick và Omit, vốn nhận một kiểu đối tượng và một danh sách khóa của nó.
Kiểu hàm và lớp: Parameters, ReturnType và các kiểu khác
| Utility type | Tác dụng | Ví dụ |
|---|---|---|
ReturnType<F> | kiểu trả về của một kiểu hàm | ReturnType<typeof createStore> |
Parameters<F> | các kiểu tham số dưới dạng tuple | Parameters<typeof fetchPage>[0] |
ConstructorParameters<C> | tham số của hàm khởi tạo lớp dưới dạng tuple | ConstructorParameters<typeof Point> |
InstanceType<C> | kiểu instance mà hàm khởi tạo tạo ra | InstanceType<typeof Point> |
ThisParameterType<F> | kiểu của tham số this trong một hàm | ThisParameterType<typeof greet> |
OmitThisParameter<F> | kiểu hàm không có tham số this | kiểu của greet.bind(obj) |
ThisType<T> | đặt kiểu của this bên trong các phương thức của object literal | dùng với noImplicitThis trong các API builder |
NoInfer<T> | ngăn tham số kiểu được suy luận từ vị trí này | fallback: NoInfer<C> |
Cần có typeof createOrder vì các tiện ích này nhận vào một kiểu, còn createOrder là một giá trị. Lớp cũng vậy: typeof Point là kiểu của hàm khởi tạo, còn Point dùng làm kiểu thì đã mang nghĩa kiểu instance.
NoInfer điều khiển nơi một generic lấy kiểu của nó:
Không có NoInfer, TypeScript sẽ suy luận C từ cả hai đối số và mở rộng nó thành "red" | "green" | "blue", nên lỗi gõ sai trong giá trị dự phòng sẽ được chấp nhận.
Kiểu chuỗi: Uppercase, Lowercase, Capitalize, Uncapitalize
| Utility type | Tác dụng | Ví dụ |
|---|---|---|
Uppercase<S> | viết hoa toàn bộ một string literal type | Uppercase<"get"> là "GET" |
Lowercase<S> | viết thường toàn bộ | Lowercase<"GET"> là "get" |
Capitalize<S> | viết hoa ký tự đầu tiên | Capitalize<"name"> là "Name" |
Uncapitalize<S> | viết thường ký tự đầu tiên | Uncapitalize<"Name"> là "name" |
Bốn kiểu này được tích hợp sẵn trong trình biên dịch chứ không được viết bằng TypeScript, và chúng hữu ích nhất bên trong template literal type, ví dụ `on${Capitalize<E>}` cho tên trình xử lý sự kiện.
Promise: Awaited
| Utility type | Tác dụng | Ví dụ |
|---|---|---|
Awaited<T> | kiểu bạn nhận được từ await, gỡ bỏ các promise lồng nhau | Awaited<Promise<Promise<number>>> là number |
Awaited<ReturnType<typeof fn>> là cách chuẩn để đặt tên cho kiểu kết quả của một hàm async mà không cần khai báo riêng.
Kết hợp các utility type
Utility type có thể lồng nhau. Một vài cách kết hợp xuất hiện đủ thường xuyên để bạn nên nhớ:
Hãy đọc một utility type lồng nhau từ trong ra ngoài: Readonly<Pick<Post, "id" | "title">> trước tiên giữ lại hai thuộc tính, rồi biến chúng thành chỉ đọc. Cũng với các thành phần đó, bạn có thể dựng một helper PartialBy chỉ biến một số khóa thành tùy chọn; trang Partial viết đầy đủ helper này.
Utility type không làm gì lúc chạy
Mọi utility type đều bị xóa khi code được biên dịch. Một giá trị có kiểu Omit<User, "password"> vẫn có thể mang password lúc chạy nếu đối tượng gốc của nó có:
Kiểu chỉ giới hạn những gì code của bạn được phép đọc. Để xóa một trường khỏi dữ liệu, hãy dùng destructuring để tách nó ra như ở các dòng cuối, và để ngăn thay đổi lúc chạy, hãy dùng Object.freeze chứ không phải Readonly. Các kiểu có sẵn đều là mapped type và conditional type viết trên một dòng, nên bạn có thể dùng chính những công cụ đó để tự viết utility type của mình.
Câu hỏi thường gặp
Utility type trong TypeScript là gì?
Là các kiểu generic có sẵn trong TypeScript dùng để biến đổi các kiểu khác: Partial<T> biến mọi thuộc tính thành tùy chọn, Pick<T, K> giữ lại một số thuộc tính, ReturnType<F> lấy kiểu trả về của một hàm, v.v. Chúng được khai báo trong thư viện chuẩn, nên bạn dùng mà không cần import gì.
Có cần import utility type không?
Không. Partial, Omit, Record, ReturnType và các kiểu còn lại là kiểu toàn cục từ các tệp thư viện có sẵn của TypeScript. Viết Partial<User> ở bất cứ đâu; không cần import và không cần package npm nào.
TypeScript có sẵn những utility type nào?
22 kiểu, tất cả khai báo trong lib.es5.d.ts: Partial, Required, Readonly, Pick, Omit, Record, Exclude, Extract, NonNullable, Parameters, ConstructorParameters, ReturnType, InstanceType, ThisParameterType, OmitThisParameter, ThisType, NoInfer, Awaited, Uppercase, Lowercase, Capitalize và Uncapitalize.
Utility type có thay đổi đối tượng lúc chạy không?
Không. Chúng chỉ mô tả kiểu và bị xóa khỏi đầu ra JavaScript. Omit<User, "password"> không xóa thuộc tính password, và Readonly<T> không đóng băng thứ gì. Để thay đổi đối tượng thật, bạn phải viết code: một mẫu destructuring với rest, Object.freeze, v.v.
Có thể tự viết utility type không?
Có. Các kiểu có sẵn đều là TypeScript bình thường: phần lớn là mapped type hoặc conditional type viết trên một dòng trong lib.es5.d.ts. type Nullable<T> = { [K in keyof T]: T[K] | null } là một utility type tự viết theo đúng cách đó.