Mình là TUẤN hiện đang là một Full-stack Web Developer tại Tokyo 😊. Nếu bạn thấy Blog này hay xin hãy cho mình một like và đăng ký để ủng hộ mình nhé 😉.
Chào mừng bạn đến với loạt bài Làm chủ TypeScript. Được nằm trong Series BÓN HÀNH TYPESCRIPT, những bài viết này sẽ giới thiệu về kiến thức và kỹ thuật cốt lõi của TypeScript dưới dạng Animations sinh động.
OK GÉT GÔ
Vấn đề
Bạn đã sử dụng Partial, Required, Readonly, and Pick utility types chưa?
Nếu bạn muốn làm chủ chúng một cách thuần thục và tạo ra các utility types
cho riêng mình thì đừng bỏ qua nội dung được đề cập trong bài viết này.
Tạo một type User
là một kịch bản phổ biến trong công việc hàng ngày. Ở đây, chúng ta có thể sử dụng TypeScript để xác định loại User trong đó tất cả các khóa được yêu cầu.
type User = { name: string; password: string; address: string; phone: string;
};
Thông thường, đối với Type User đã được khai báo, chúng ta chỉ cho phép sửa đổi một số thông tin. Tại thời điểm này, chúng ta có thể xác định một loại UserPartial mới đại diện cho loại đối tượng User cần cập nhật, trong đó tất cả các khóa là tùy chọn.
type UserPartial = { name?: string; password?: string; address?: string; phone?: string; };
Đối với kịch bản xem thông tin user, chúng ta hy vọng rằng tất cả các khóa trong loại đối tượng tương ứng với đối tượng user đều ở chế độ chỉ đọc (Readonly). Đối với yêu cầu này, chúng ta có thể xác định loại User chỉ đọc.
type ReadonlyUser = { readonly name: string; readonly password: string; readonly address: string; readonly phone: string;
};
Xem lại ba Type liên quan đến user đã được xác định, bạn sẽ thấy rằng chúng chứa rất nhiều code trùng lặp.
Vậy làm cách nào để có thể giảm bớt code trùng lặp trong các loại trên? Câu trả lời là bạn có thể sử dụng các Mapped Types, là các Type chung có thể được sử dụng để ánh xạ loại đối tượng ban đầu sang loại đối tượng mới.
Mapped Type
Cú pháp cho các loại ánh xạ như sau:
Trường hợp P in K
tương tự như câu lệnh in
trong JavaScript, được sử dụng để lặp qua tất cả các loại trong loại K và biến loại T, được sử dụng để biểu thị bất kỳ loại nào trong TypeScript.
Bạn cũng có thể sử dụng các cú pháp sửa đổi bổ sung chỉ đọc và dấu chấm hỏi (?) trong quá trình mapping. Các cú pháp sửa đổi tương ứng được thêm vào và loại bỏ bằng cách thêm các tiền tố dấu cộng(+) và dấu trừ(-). Mặc định là sử dụng dấu cộng nếu không thêm tiền tố.
Bây giờ chúng ta có thể tóm tắt cú pháp của các loại Mapping phổ biến.
{ [ P in K ] : T }
{ [ P in K ] ?: T }
{ [ P in K ] -?: T }
{ readonly [ P in K ] : T }
{ readonly [ P in K ] ?: T }
{ -readonly [ P in K ] ?: T }
Sau khi xem cú pháp của các loại Mapped Types
, giờ hãy đến một số ví dụ.
Hãy xem cách xác định lại loại UserPartial
bằng cách sử dụng các Mapped Types.
type MyPartial<T> = { [P in keyof T]?: T[P];
};
type UserPartial = MyPartial<User>;
Trong đoạn code trên, chúng ta xác định Mapped Types MyPartial
và sau đó sử dụng nó để ánh xạ loại User thành loại UserPartial
. Toán tử keyof được sử dụng để lấy tất cả các khóa của một loại và kiểu trả về của nó là kiểu kết hợp. Biến loại P thay đổi thành một loại khác với mỗi lần duyệt, T[P]
, tương tự như cú pháp truy cập Properties và được sử dụng để lấy loại value tương ứng với một Properties của loại đối tượng.
Hãy xem ảnh minh họa quy trình thực thi hoàn chỉnh của Mapped Types MyPartial
, nếu chưa rõ, bạn có thể xem nhiều lần để hiểu sâu hơn về Mapped Types TypeScript.
TypeScript 4.1 cho phép chúng ta ánh xạ lại các khóa trong các Mapped Types bằng mệnh đề as
. Cú pháp của nó như sau:
type MappedTypeWithNewKeys<T> = { [K in keyof T as NewKeyType]: T[K] // ^^^^^^^^^^^^^ // New Syntax!
}
Trong đó loại NewKeyType
phải là một kiểu con của string | number | symbol union type. Sử dụng mệnh đề as
, chúng ta có thể xác định Getters utility type và tạo ra loại Getter tương ứng cho loại đối tượng.
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
interface Person { name: string; age: number; location: string;
}
type LazyPerson = Getters<Person>;
// {
// getName: () => string;
// getAge: () => number;
// getLocation: () => string;
// }
Trong đoạn code trên, vì loại được trả về bởi keyof T
có thể chứa kiểu ký hiệu (Symbol type) và kiểu Viết hoa chữ cái đầu (Capitalize utility). Nó yêu cầu kiểu được xử lý cần phải là một kiểu con của loại string
, nên cần phải lọc kiểu bằng toán tử &
.
Ngoài ra, trong quá trình Mapping lại các Keys, chúng ta có thể lọc các Keys bằng cách trả về never type
.
// Remove the 'kind' property
type RemoveKindField<T> = { [K in keyof T as Exclude<K, "kind">]: T[K]
};
interface Circle { kind: "circle"; radius: number;
}
type KindlessCircle = RemoveKindField<Circle>;
// type KindlessCircle = {
// radius: number;
// };
Sau khi đọc bài viết này, mình chắc rằng bạn đã hiểu chức năng của các mapped types
và cách implement một số utility types
bên trong TypeScript.
Mapping là một trong những kiến thức nền tảng và cốt lõi để bạn có thể tiến xa hơn trên con đường chinh phục những khái niệm nâng cao khác trong Typescript.
Roundup
Như mọi khi, mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.
Cảm ơn và hẹn gặp lại các bạn trong những bài viết tiếp theo! 😍
Nếu bạn thấy Blog này hay xin hãy cho mình một like và đăng ký để ủng hộ mình nhé. Thank you.😉