Bạn có đang gặp khó khăn trong việc quản lý kiểu dữ liệu trong TypeScript? Bài viết này sẽ giúp bạn hiểu rõ về Type Guard và Narrowing, hai kỹ thuật quan trọng giúp đảm bảo tính an toàn và chính xác của code. Chúng ta sẽ đi sâu vào cách chúng hoạt động và cung cấp các ví dụ minh họa dễ hiểu.
TypeScript là một ngôn ngữ strongly-typed, nghĩa là kiểu dữ liệu được kiểm tra tại thời điểm biên dịch. Tuy nhiên, trong nhiều trường hợp, kiểu dữ liệu của một biến có thể không xác định (ví dụ: union type). Type Guard giúp bạn xác định kiểu dữ liệu chính xác của biến tại runtime, từ đó tránh các lỗi tiềm ẩn.
Ví dụ, xét một hàm có thể nhận vào một số hoặc một chuỗi:
function processInput(input: string | number) {
// Làm thế nào để biết input là string hay number?
}
Nếu không có Type Guard, bạn sẽ gặp khó khăn trong việc xử lý `input` một cách an toàn. TypeScript sẽ không cho phép bạn gọi các phương thức chỉ dành cho string (ví dụ: `toUpperCase()`) nếu `input` có thể là number.
Narrowing là quá trình thu hẹp phạm vi kiểu dữ liệu của một biến. Type Guard là một trong những cách để thực hiện Narrowing. Khi TypeScript "nhận ra" rằng một biến chắc chắn thuộc một kiểu cụ thể, nó sẽ thu hẹp kiểu dữ liệu của biến đó trong phạm vi nhất định.
function processInput(input: string | number) {
if (typeof input === "string") {
// TypeScript biết input là string ở đây
console.log(input.toUpperCase());
} else {
// TypeScript biết input là number ở đây
console.log(input * 2);
}
}
class Animal {}
class Dog extends Animal {
bark() {
console.log("Woof!");
}
}
function makeSound(animal: Animal) {
if (animal instanceof Dog) {
// TypeScript biết animal là Dog ở đây
animal.bark();
}
}
interface Bird {
fly(): void;
}
interface Fish {
swim(): void;
}
function move(animal: Bird | Fish) {
if ("swim" in animal) {
// TypeScript biết animal là Fish ở đây
animal.swim();
} else {
// TypeScript biết animal là Bird ở đây
animal.fly();
}
}
interface Bird {
fly(): void;
}
interface Fish {
swim(): void;
}
function isFish(animal: Bird | Fish): animal is Fish {
return (animal).swim !== undefined;
}
function move(animal: Bird | Fish) {
if (isFish(animal)) {
// TypeScript biết animal là Fish ở đây
animal.swim();
} else {
// TypeScript biết animal là Bird ở đây
animal.fly();
}
}
Khi làm việc với các đối tượng phức tạp, Discriminated Unions là một kỹ thuật mạnh mẽ để sử dụng Type Guard. Discriminated Unions sử dụng một thuộc tính chung (thường gọi là `kind`) để phân biệt các loại khác nhau trong một union type.
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
}
}
Trong ví dụ này, thuộc tính `kind` giúp TypeScript xác định chính xác loại hình và truy cập các thuộc tính tương ứng một cách an toàn.
Type Guard và Narrowing là những công cụ không thể thiếu để viết code TypeScript an toàn và hiệu quả. Bằng cách sử dụng chúng, bạn có thể tránh các lỗi runtime liên quan đến kiểu dữ liệu và tận dụng tối đa sức mạnh của TypeScript.
Hãy thử nghiệm với các ví dụ trên và áp dụng chúng vào dự án của bạn để trải nghiệm sự khác biệt!
Bài viết liên quan