TypeScript với Kiểu Dữ Liệu Cơ Bản – 3
- 16-01-2024
- Toanngo92
- 0 Comments
Tiếp theo từ phần 2 của loạt bài “Typescript với Kiểu Dữ Liệu cơ bản,” chúng ta đã thu thập một số kiến thức quan trọng về Typescript. Trong bài viết này, chúng ta sẽ khám phá thêm về một số Typescript như union, intersection, và utility types.
Mục lục
Union Type:
Chúng ta có thể tạo một loại dữ liệu mới với tên là Role để đại diện cho quyền của người dùng, chẳng hạn như Admin, Guest, và User. Mỗi người dùng sẽ được gán một trong ba quyền này, và chúng ta có thể định nghĩa như sau:
type Role = "Admin" | "User" | "Guest";
Union Type cũng được sử dụng khi khai báo kiểu dữ liệu cho một biến có thể nhận giá trị từ nhiều kiểu khác nhau:
let age: number | string = '5';
Chúng ta cũng thấy Union Type trong khai báo các loại dữ liệu cho biến và trong hàm:
type NetworkLoadingState = {
state: "loading";
};
type NetworkFailedState = {
state: "failed";
code: number;
};
type NetworkState =
| NetworkLoadingState
| NetworkFailedState;
const state: NetworkState = {
state: "loading"
};
state.code = {}; // Lỗi: Property 'code' không tồn tại trong type 'NetworkLoadingState'.
Chúng ta đã định nghĩa hai kiểu dữ liệu là NetworkLoadingState
và NetworkFailedState
, sau đó tạo thêm một kiểu dữ liệu Union là NetworkState
. Khi sử dụng const state: NetworkState
, chúng ta có thể truy cập vào trường dữ liệu “state” vì cả hai kiểu đều chia sẻ trường này.
Tuy nhiên, khi thử truy cập vào state.code, một lỗi xuất hiện. Nguyên nhân là vì Union Type có thể là một trong hai kiểu, và có thể trường “code” chỉ tồn tại trong NetworkLoadingState
mà thôi.
Dưới đây là một cách khác để miêu tả vấn đề trên:
// Định nghĩa các kiểu dữ liệu
type NetworkLoadingState = {
state: 'loading';
code: number;
};
type NetworkFailedState = {
state: 'failed';
message: string;
};
// Tạo Union Type
type NetworkState = NetworkLoadingState | NetworkFailedState;
// Sử dụng Union Type
const state: NetworkState = /* ... */;
// Kiểm tra và truy cập trường dữ liệu
if (state.state === 'loading') {
console.log(state.code);
} else if (state.state === 'failed') {
console.log(state.message);
}
Về ví dụ thứ hai với hàm printId
, bạn đã mô tả rõ vấn đề khi cố gắng sử dụng phương thức toUpperCase
cho cả số và chuỗi. Một cách giải quyết là kiểm tra kiểu dữ liệu của tham số sử dụng typeof
, hãy xem đoạn mã bên dưới:
function printId(id: number | string) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}
Việc này giúp đảm bảo rằng bạn chỉ gọi toUpperCase
khi tham số là một chuỗi.
Intersection Type
Intersection Type là một khái niệm ngược lại với Union Type trong TypeScript. Nó cho phép kết hợp các thuộc tính từ nhiều Type khác nhau để tạo ra một Type mới, sử dụng toàn bộ các keys từ các Types gốc.
Dưới đây là một cách trình bày khác về Intersection Type và một số quy tắc khi sử dụng:
// Định nghĩa các kiểu dữ liệu
type Student = {
name: string;
age: number;
}
type Person = {
name: string;
}
// Sử dụng Intersection Type
type People = Person & Student;
// Dùng Intersection Type
const people: People = {
name: 'xiaoming',
age: 24,
};
// Lỗi nếu thiếu trường 'age'
const invalidPeople: People = {
name: 'xiaoming',
// Error: Property 'age' is missing in type '{ name: string; }' but required in type 'People'.
};
// Lỗi nếu kiểu không nhất quán
const inconsistentPeople: People = {
name: 'xiaoming',
age: '24', // Error: Type 'string' is not assignable to type 'number'.
};
// Tránh sử dụng giá trị không xác định cho key, gây ra never
type StudentWithName = {
name: 'evondev';
};
type PersonWithName = {
name: 'tuanpzo';
};
// Error: Type 'never' is not assignable to type 'People'
type PeopleWithNames = PersonWithName & StudentWithName;
Intersection Type giúp đảm bảo rằng một đối tượng của Type mới sẽ có tất cả các thuộc tính từ cả hai Types gốc. Tuy nhiên, cần lưu ý về sự nhất quán trong việc đặt kiểu cho từng trường dữ liệu và tránh sử dụng giá trị không xác định để tránh trả về kiểu never
.
Utility Types:
Có nhiều loại Utility Types hữu ích trong TypeScript, và dưới đây là một số ví dụ về những loại phổ biến:
Partial<Type>
Partial<Type>
tạo ra một Type mới dựa trên Type gốc, nhưng biến tất cả các keys thành optional (có thể có hoặc không).
type Todo = {
title: string;
description: string;
};
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
updateTodo({ title: "test", description: "" }, { description: "test" });
Required<Type>
Required<Type>
làm cho tất cả các keys trong Type trở nên bắt buộc, ngay cả khi ban đầu chúng có thể là optional.
type Todo = {
title?: string;
description?: string;
};
function updateTodo(todo: Todo, fieldsToUpdate: Required<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
// Lỗi vì 'title' là bắt buộc, nhưng không được truyền vào
updateTodo({ title: "test", description: "" }, { description: "test" });
Record<Keys, Type>
Record<Keys, Type>
tạo ra một Type có các keys và giá trị tương ứng. Dưới đây là một ví dụ với danh sách các chú mèo và thông tin của chúng.
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>
Readonly<Type>
làm cho tất cả các keys trong Type trở nên chỉ đọc (không thể sửa đổi).
interface Todo {
title: string;
}
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
// Lỗi vì 'title' là chỉ đọc
todo.title = "Hello";
Pick<Type, Keys>
Pick<Type, Keys>
chọn ra các keys mà bạn quan tâm từ một Type.
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>
Omit<Type, Keys>
loại bỏ các keys không cần thiết từ Type.
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>
Extract<Type, Union>
trích xuất các loại từ mộ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>
Exclude<UnionType, ExcludedMembers>
loại bỏ các thành viên cụ thể từ một Union Type.
type T0 = Exclude<"a" | "b" | "c", "a">;
// type T0 = "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
// type T1 = "c"
type T2 = Exclude<string | number | (() => void), Function>;
// type T2 = string | number;
NonNullable<Type>
NonNullable<Type>
loại bỏ undefined và null ra khỏi Type.
type T0 = NonNullable<string | number | undefined>;
// type T0 = string | number;
Những Utility Types trong TypeScript mang lại sự tiện lợi và khả năng linh hoạt lớn khi bạn đang làm việc với kiểu dữ liệu. Dưới đây là một số điểm mở rộng để giúp bạn hiểu rõ hơn về tầm quan trọng của chúng:
- Giảm Độ Lặp Lại (Boilerplate): Các Utility Types giúp giảm độ lặp lại trong mã nguồn của bạn bằng cách cung cấp các công cụ linh hoạt để xử lý kiểu dữ liệu. Thay vì phải đặt lại kiểu cho từng thuộc tính hoặc biến, bạn có thể sử dụng các Utility Types để áp dụng các biến đổi phổ biến một cách dễ dàng.
- An Toàn và Dễ Bảo Trì: Việc sử dụng các Utility Types giúp tăng cường tính an toàn của mã nguồn TypeScript. Bạn có thể định nghĩa các quy tắc và kiểu một lần và tái sử dụng chúng ở nhiều nơi trong mã nguồn, giúp giảm rủi ro lỗi và dễ bảo trì hơn.
- Kiểm Soát Kiểu Dữ Liệu: Các Utility Types cho phép bạn kiểm soát chặt chẽ kiểu dữ liệu trong ứng dụng của mình. Bạn có thể xác định rõ ràng những gì được phép và không được phép trong một kiểu dữ liệu cụ thể, giúp ngăn chặn lỗi và làm cho mã nguồn dễ hiểu hơn.
- Tích Hợp Tốt Với IDE và Tooling: TypeScript hiểu rõ về các Utility Types và cung cấp hỗ trợ tốt trong quá trình phát triển. IDE sẽ có thể đưa ra gợi ý thông minh, đơn giản hóa quá trình refactor, và cung cấp kiểm tra lỗi tĩnh hiệu quả dựa trên những quy tắc được xác định bởi các Utility Types.
- Hỗ Trợ Tốt Trong Cộng Đồng: Do Utility Types được sử dụng phổ biến trong cộng đồng TypeScript, việc chia sẻ mã nguồn và kiến thức về cách sử dụng chúng trở nên dễ dàng hơn. Bạn có thể học hỏi từ các mã nguồn mở, thảo luận và chia sẻ những cách sử dụng hiệu quả Utility Types.
Tóm lại, sử dụng Utility Types không chỉ giúp tăng cường tính linh hoạt và mạnh mẽ của TypeScript mà còn làm cho quá trình phát triển và bảo trì mã nguồn trở nên hiệu quả và dễ quản lý hơn.