Menu

Utility type trong TypeScript: danh sách đầy đủ và ví dụ

Mọi utility type có sẵn của TypeScript ở cùng một chỗ: Partial, Required, Readonly, Pick, Omit, Record, Exclude, Extract, NonNullable, Parameters, ReturnType, Awaited, các kiểu chuỗi và nhiều hơn nữa, mỗi kiểu kèm mô tả một dòng và ví dụ chạy được.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

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 typeTác dụngVí dụ
Partial<T>biến mọi thuộc tính thành tùy chọnPartial<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 readonlyReadonly<State>
Pick<T, K>chỉ giữ lại các khóa KPick<User, "id" | "name">
Omit<T, K>bỏ đi các khóa KOmit<User, "password">
Record<K, V>một kiểu đối tượng có khóa K và giá trị VRecord<"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 typeTác dụngVí dụ
Exclude<U, M>bỏ các thành viên union gán được cho MExclude<"a" | "b" | "c", "a"> là "b" | "c"
Extract<U, M>giữ các thành viên union gán được cho MExtract<string | number, number> là number
NonNullable<T>bỏ null và undefinedNonNullable<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 typeTác dụngVí dụ
ReturnType<F>kiểu trả về của một kiểu hàmReturnType<typeof createStore>
Parameters<F>các kiểu tham số dưới dạng tupleParameters<typeof fetchPage>[0]
ConstructorParameters<C>tham số của hàm khởi tạo lớp dưới dạng tupleConstructorParameters<typeof Point>
InstanceType<C>kiểu instance mà hàm khởi tạo tạo raInstanceType<typeof Point>
ThisParameterType<F>kiểu của tham số this trong một hàmThisParameterType<typeof greet>
OmitThisParameter<F>kiểu hàm không có tham số thiskiể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 literaldù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àyfallback: 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 typeTác dụngVí dụ
Uppercase<S>viết hoa toàn bộ một string literal typeUppercase<"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ênCapitalize<"name"> là "Name"
Uncapitalize<S>viết thường ký tự đầu tiênUncapitalize<"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 typeTác dụngVí dụ
Awaited<T>kiểu bạn nhận được từ await, gỡ bỏ các promise lồng nhauAwaited<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 đó.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu