Các Types (kiểu dữ liệu) trong TypeScript (P3)
Tác giả: Trần Anh Tuấn
Tiếp nối ở bài trước, chúng ta đã học được cũng nhiều kiến thức về Typescript rồi. Ở bài viết này chúng ta sẽ cùng tìm hiểu thêm về nhiều Types hay ho khác của Typescript như union, intersection, utilitiy,…
Lưu ý khi đặt tên Type hoặc Interface thì nên đặt tên dễ hiểu và chữ cái đầu IN HOA nhé. Ví dụ như Permissions, UserName, Role…
>> TypeScript là gì? Tại sao nên chọ TypeScript? << Đọc bài viết này để giải đáp
Union Type
Mình muốn tạo ra một Type có tên là Role để chứa các quyền của người dùng như là Admin, Guest, và User. Người dùng sẽ có 1 trong 3 quyền này cho nên chúng ta sẽ viết nó như sau
type Role = "Admin" | "User" | "Guest";
Ngoài ra Union Type còn dùng khi khai báo kiểu dữ liệu cho biến mà mình có đề cập đến cho các bạn ở những bài trước như là
let age: number | string = '5';
Đi sâu hơn vào nó thì sẽ có một vài trường hợp như sau để các bạn nhớ. Vì là Union nghĩa là hoặc Type này hoặc Type kia.
type NetworkLoadingState = { state: "loading"; }; type NetworkFailedState = { state: "failed"; code: number; }; type NetworkState = | NetworkLoadingState | NetworkFailedState; const state: NetworkState = { state: "loading" }; state.code = {}; // Property 'code' does not exist on type 'NetworkLoadingState'.
Mình khai báo 2 Types là NetworkLoadingState và NetworkFailedState, sau đó mình khai báo tiếp 1 Type là NetworkState là Union Type. Khi mình dùng const state: NetworkState
thì mình sử dụng được là state
bởi vì 2 Types đều có chung key là state
.
Tuy nhiên khi mình dùng state.code
thì lại báo lỗi, mình có ghi là bởi vì Union là hoặc cho nên có thể là nó chạy vào NetworkLoadingState, mà thằng này thì không có key là code
.
function printId(id: number | string) { console.log(id.toUpperCase()); // Property 'toUpperCase' does not exist on type 'string | number'. // Property 'toUpperCase' does not exist on type 'number'. }
Một ví dụ khác khi khai báo params cho function và sử dụng các phương thức dựa vào params. Nếu là string thì có thể sử dụng .toUpperCase()
tuy nhiên nếu là number thì sẽ không có các phương thức đó. Cho nên chương trình sẽ bắn ra lỗi ngay lập tức.
Để giải quyết vấn đề đó thì có thể kiểm tra Type của params dựa vào typeof như thế này
function printId(id: number | string) { if (typeof id === "string") { // In this branch, id is of type 'string' console.log(id.toUpperCase()); } else { // Here, id is of type 'number' console.log(id); } }
Intersection Type
Ngược lại với Union Type đó chính là Intersection Type(&
và). Và Type này và Type kia. Nghĩa là sử dụng được hết toàn bộ các keys từ nhiều Types luôn.
type Student = { name: string; age: number; } type Person = { name: string; } type People = Person & Student; const people: People = { name: 'xiaoming', } // Property 'age' is missing in type '{ name: string; }' but required in type 'Student'.
Như ví dụ trên thì các bạn sẽ thấy mình không truyền vào key là age
nên chương trình sẽ báo lỗi ngay vì nó bắt buộc khi sử dụng Intersection Type. Một điều nữa là phải nhất quán khi khai báo nhé, chú ý age là number nhưng lại sử dụng là string cũng sẽ lỗi.
const people: People = { name: 'xiaoming', age: '24' } // Type 'string' is not assignable to type 'number'.
Một điều cuối, cẩn thận khi khai báo như thế này, khi các bạn không xác định Type cho key mà là một giá trị nào đó thì 2 Type đó không được trùng key, nếu không thì nó sẽ trả ra never, mà never chúng ta đã học ở bài phần 1 rồi.
Type never không gán được bất kỳ giá trị nào!
type Student = { name: 'evondev'; } type Person = { name: 'tuanpzo'; } type People = Person & Student; // never // Error const people: People = { name: 'xiaoming', }
Tham khảo việc làm Typescript hấp dẫn tại TopDev
Utility Types
Utility types thì có rất nhiều tuy nhiên ở đây mình sẽ liệt kê kèm ví dụ minh họa cho các bạn những cái thông dụng thôi nhé.
Partial<Type>
Nó sẽ tạo ra một Type mới dựa vào Type gốc nhưng sẽ thay đổi toàn bộ keys thành optional(không bắt buộc dấu ?)
type Todo = { title: string; description: string; }; function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) { return { ...todo, ...fieldsToUpdate }; } updateTodo({ title: "test", description: "" }, { description: "test" });
Mình có type là Todo với 2 keys là bắt buộc, các bạn để ở chỗ fieldsToUpdate mình có dùng Partial<Todo>
cho nên param fieldsToUpdate lúc này nó sẽ trông như code ở dưới.
Cho nên khi mình gọi hàm updateTodo thì param đầu tiên buộc phải đầy đủ title
và description
, tuy nhiên param thứ 2 thì mình chỉ cần truyền description
là được rồi, không báo lỗi gì cả.
type Todo = { title?: string; description?: string; };
Required<Type>
Ngược lại với Partial<Type> ở trên thì thằng này sẽ biến các keys thành bắt buộc, cho dù ban đầu nó là optional(?) đi chăng nữa
type Todo = { title?: string; description?: string; }; function updateTodo(todo: Todo, fieldsToUpdate: Required<Todo>) { return { ...todo, ...fieldsToUpdate }; } updateTodo({ title: "test", description: "" }, { description: "test" } Error); // Property 'title' is missing in type '{ description: string; }' but required in type 'Required<Todo>'
Record<Keys, Type>
Nó sẽ tạo ra Type có các Keys và Value. Mình sẽ nói trường hợp đơn giản trước cho các bạn như này, nhìn vào rất dễ hiểu đúng không ? Keys là string và Value là Number.
const example: Record<string, number> = { a: 1, b: 2, c: 3, };
Tiếp tục mình khai báo 2 Type tương ứng là CatInfo và CatName sau đó mình lại sử dụng Record để tạo ra các thông tin cho các chú mèo như sau. Lúc này từng chú mèo(CatName) sẽ có các thông tin như nhau(CatInfo) là age
và breed
. Quá xịn phải không nào.
type CatInfo = { age: number; breed: string; } type CatName = "miffy" | "boris" | "mordred"; const cats: Record<CatName, CatInfo> = { miffy: { age: 10, breed: "Persian" }, boris: { age: 5, breed: "Maine Coon" }, mordred: { age: 16, breed: "British Shorthair" }, };
Readonly<Type>
Như tên gọi của nó, sẽ làm cho các keys trở nên Readonly(chỉ đọc chứ không được sửa). Nhìn ví dụ cho dễ thông não nè.
Interface cũng tương tự như Type thôi. Mình sẽ nói chi tiết về sự khác nhau giữa chúng ở bài sau nhé.
interface Todo { title: string; } const todo: Readonly<Todo> = { title: "Delete inactive users", }; todo.title = "Hello"; // Cannot assign to 'title' because it is a read-only property.
Pick<Type, Keys>
Pick nghĩa là chọn ra những keys mà các bạn muốn từ một Type nào đó. Đôi khi trong quá trình code có rất nhiều keys từ Type, nhưng chúng ta chỉ sử dụng vài cái trong đó, lúc này Pick<Type>
là một lựa chọn tuyệt vời.
Mình lấy ra 2 keys là title
và completed
từ interface Todo
interface Todo { title: string; description: string; completed: boolean; } type TodoPreview = Pick<Todo, "title" | "completed">; const todo: TodoPreview = { title: "Clean room", completed: false, };
Omit<Type, Keys>
Ngược lại với Pick<Type, Keys> thì Omit<Type, Keys>
sẽ lấy toàn bộ các keys từ Type nào đó sau đó loại bỏ ra một số keys(string hoặc union) không cần thiết.
interface Todo { title: string; description: string; completed: boolean; createdAt: number; } type TodoPreview = Omit<Todo, "description">; // remove description const todo: TodoPreview = { title: "Clean room", completed: false, createdAt: 1615544252770, };
Extract<Type, Union>
Dùng để trích xuất Type từ Union Type
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // type T0 = "a" type T1 = Extract<string | number | (() => void), Function>; // type T1 = () => void type Shape = | { kind: "circle"; radius: number } | { kind: "square"; x: number } | { kind: "triangle"; x: number; y: number }; type T2 = Extract<Shape, { kind: "circle" }> // type T2 = { kind: "circle"; radius: number;}
Exclude<UnionType, ExcludedMembers>
Ngược lại với Extract ở trên thì nó sẽ loại bỏ những ExcludedMembers từ UnionType
// remove a type T0 = Exclude<"a" | "b" | "c", "a">; // type T0 = "b" | "c" // remove a and b type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // type T1 = "c" // remove Function type T2 = Exclude<string | number | (() => void), Function>; // type T2 = string | number;
NonNullable<Type>
Dùng để loại bỏ undefined và null ra khỏi Type
type T0 = NonNullable<string | number | undefined>;
// type T0 = string | number;
Tạm kết
Phần 3 tạm dừng ở đây nhé các bạn. Ở bài này chúng ta đã học thêm được khá nhiều Types mới rồi, kiến thức cũng có thêm 1 chút. Ở bài số 4 chúng ta sẽ tìm hiểu sự biệt giữa interface và type, cũng như tìm hiểu thêm về Mapped và Index Type cùng một số kiến thức liên quan tới chúng.
Bài viết gốc được đăng tải tại evondev.com
Nhiều bài học TypeScript hơn tại đây:
- Giới thiệu Types (kiểu dữ liệu) trong TypeScript (P2)
- Giới thiệu Types (kiểu dữ liệu) trong TypeScript (P1)
Xem thêm Việc làm IT hấp dẫn trên TopDev
- B BenQ RD Series – Dòng Màn Hình Lập Trình 4k+ Đầu Tiên Trên Thế Giới
- F Framework nào tốt nhất cho dự án của bạn? – Checklist chi tiết
- K Kinh nghiệm xử lý responsive table hiệu quả
- S Stackoverflow là gì? Bí kíp tận dụng Stack Overflow hiệu quả
- 7 7 kinh nghiệm hữu ích khi làm việc với GIT trong dự án
- B Bài tập Python từ cơ bản đến nâng cao (có lời giải)
- B Bảo mật API là gì? Một số nguyên tắc và kỹ thuật cần biết
- H Hướng dẫn cài đặt và tự học lập trình Python cơ bản từ A-Z
- C Chinh Phục Phân Tích Dữ Liệu Với Pandas Trong Python: Hướng Dẫn Từng Bước
- D Display CSS là gì? Cách khai báo và sử dụng thuộc tính display trong CSS