- vừa được xem lúc

TypeScript Generics: Hướng dẫn toàn diện

1 1 75

Người đăng: Gung Typical

Theo Viblo Asia

TypeScript Generics cho phép lập trình viên viết code có thể tái sử dụng với nhiều kiểu dữ liệu khác nhau mà vẫn đảm bảo an toàn kiểu. Chúng rất cần thiết để xây dựng các ứng dụng TypeScript mạnh mẽ và có khả năng mở rộng.

Tổng quan về TypeScript Generics

Để đảm bảo code minh bạch và dễ quản lý, Typescript yêu cầu việc quản lý an toàn và hiệu quả nhiều loại dữ liệu. Một trong những tính năng cốt lõi của Typescript là generics, cho phép tạo các hàm, lớp và interface vẫn tuân thủ các ràng buộc kiểu nghiêm ngặt. Generics cho phép bạn viết ít code hơn, ít lỗi hơn và quan trọng nhất là xây dựng các thành phần linh hoạt cho các kiểu dữ liệu khác nhau.

Bài viết này khám phá những điều cần thiết về TypeScript generics, bao gồm cách sử dụng chúng trong hàm, lớp và interface, đồng thời minh họa cách chúng làm cho code linh hoạt và mạnh mẽ.

Generic Typescript là gì?

Generics trong TypeScript cho phép định nghĩa code với các kiểu giữ chỗ, cho phép code linh hoạt, mở rộng và tái sử dụng được trong khi vẫn đảm bảo an toàn kiểu. TypeScript thực hiện kiểm tra an toàn kiểu trong thời gian biên dịch như một trình giữ chỗ xác định kiểu generic. Khi thành phần được triển khai, kiểu thực tế sẽ thay thế trình giữ chỗ.

Kỹ thuật này giúp việc quản lý và giảm trùng lặp dễ dàng hơn vì bạn không cần triển khai riêng biệt cho từng kiểu dữ liệu. Nếu không có generics, bạn sẽ phải viết nhiều phiên bản của một hàm hoặc lớp để xử lý các kiểu dữ liệu khác nhau, dẫn đến trùng lặp code. Generics cho phép một triển khai duy nhất có thể tái sử dụng cho nhiều loại khác nhau trong khi vẫn giữ được kiểm tra kiểu tĩnh.

Các ví dụ code trong phần tiếp theo sẽ giúp bạn hiểu sự khác biệt này.

Khi nào nên sử dụng Typescript generic?

Generics có thể được sử dụng trên các phần khác nhau của TypeScript để giúp quản lý các kiểu hiệu quả hơn. Chúng rất hữu ích trong các hàm, interface, lớp và các cấu trúc khác, nơi tính linh hoạt là rất quan trọng.

1. Kiểu Generic trong hàm

Generics thường được áp dụng trong các hàm để giảm trùng lặp. Ví dụ: hãy xem xét một hàm nhận một chuỗi hoặc một số làm tham số.

function identity(value: any): any { return value;
}
const result1 = identity(42); // result1: any
const result2 = identity("hello"); // result2: any

Hàm này hoạt động tốt. Nhưng nó sử dụng kiểu any, có nghĩa là TypeScript mất dấu kiểu cụ thể. Kết quả là, giá trị trả về được gõ là any và TypeScript không thể thực thi an toàn kiểu nữa. Nếu chúng ta cần duy trì an toàn kiểu, chúng ta sẽ phải viết hai hàm khác nhau, một hàm trả về một chuỗi trong khi hàm kia trả về một số.

Tuy nhiên, cách tiếp cận đó sẽ làm tăng trùng lặp code. Chúng ta có thể cải thiện hàm trên bằng cách sử dụng generics để bảo toàn thông tin kiểu.

function identity<T>(value: Type): T { return value;
}
const result1 = identity<number>(42); // result1: number
const result2 = identity<string>("hello"); // result2: string

T đại diện cho kiểu mà phương thức sử dụng trong trường hợp này. Nếu có, TypeScript sẽ xác nhận rằng kiểu đầu vào và kiểu trong tham số trả về là giống nhau.

Ngoài ra, chúng ta có thể định nghĩa hàm mà không cần định nghĩa rõ ràng kiểu tham số.

const result3 = identity(100); // result3: number
const result4 = identity("world"); // result4: string

Trong TypeScript, bạn có thể sử dụng nhiều hơn một tham số kiểu generic khi làm việc với nhiều kiểu trong một hàm hoặc thành phần duy nhất.

Ví dụ: bạn có thể muốn một hàm nhận hai kiểu đầu vào khác nhau và trả về chúng dưới dạng một cặp.

function multipleParams<T, U>(first: T, second: U): [T, U] { return [first, second];
}
const result1 = multipleParams<string, number>("hello", 42); // result1: [string, number]
const result2 = multipleParams<string, number>("hello", "world"); // result2: gives a type error

Trong trường hợp này, hàm trả về một tuple với phần tử đầu tiên thuộc kiểu T và phần tử thứ hai thuộc kiểu U. Điều này cho phép hàm xử lý an toàn kiểu của hai kiểu riêng biệt.

2. Các kiểu mặc định trong TypeScript

Trong TypeScript, bạn có thể cung cấp một kiểu mặc định cho một generic, làm cho nó trở thành tùy chọn. Nếu không có kiểu nào được cung cấp, TypeScript sẽ sử dụng kiểu mặc định.

function createArray<T = string>(length: number, value: T): T[] { return Array(length).fill(value);
} const stringArray = createArray(3, "hello"); // T defaults to string, so stringArray is a string array
const numberArray = createArray<number>(3, 42); // T is explicitly set to a number, so numberArray is a number array

Trong ví dụ này, tham số kiểu T mặc định là string. Nếu nhà phát triển không chỉ ra một kiểu cụ thể khi họ gọi hàm, T sẽ là một chuỗi theo mặc định.

3. Generic Interfaces

TypeScript generics cũng có thể được áp dụng cho các interface. Hãy tưởng tượng bạn muốn định nghĩa một interface Box với một giá trị thuộc kiểu any.

interface Box { value: any;
}
const numberBox: Box = { value: 123 }; // correct
const stringBox: Box = { value: "hello" }; // correct

Điều này tương đương với ví dụ về hàm generic; code này cũng sẽ hoạt động mà không có vấn đề gì vì chúng ta chưa định nghĩa một kiểu cụ thể. Tuy nhiên, vì giá trị được gõ là any, chúng ta có thể gặp phải lỗi liên quan đến kiểu.

Để đảm bảo kiểu, chúng ta có thể định nghĩa một generic interface ở đây.

interface Box<Type> { value: Type;
}
const numberBox: Box<number> = { value: 123 }; // number
const stringBox: Box<string> = { value: "hello" }; // string
const stringBox2: Box<string> = { value: 123 }; // incorrect

Interface này là generic và kiểu giá trị của nó bị ràng buộc chặt chẽ với biến Type. Biến Type có thể được chỉ định là số hoặc chuỗi trong khi tạo một instance để TypeScript đảm bảo rằng các kiểu phù hợp được tuân thủ.

4. Các lớp chung

Các lớp cũng có thể được viết bằng generic để xử lý các kiểu khác nhau trong khi vẫn duy trì tính an toàn của kiểu. Hãy tạo một lớp Storage có thể lưu trữ và truy xuất các giá trị của bất kỳ kiểu nào.

class Storage { private data: any; setItem(item: any): void { this.data = item; } getItem(): any { return this.data; }
}
const storage = new Storage();
storage.setItem(123);
const item = storage.getItem();

Lớp này hoạt động, nhưng vì dữ liệu có kiểu any, phương thức getItem trả về any, loại bỏ tính an toàn của kiểu. Vì vậy, chúng ta có thể viết lại lớp bằng cách sử dụng generic để cải thiện tính an toàn của kiểu.

class Storage<T> { private data: T; setItem(item: T): void { this.data = item; } getItem(): T { return this.data; }
}
const numberStorage = new Storage<number>();
numberStorage.setItem(123);
const item = numberStorage.getItem();

Trong trường hợp này, kiểu T được lớp Storage sử dụng . Typescript đảm bảo dữ liệu là chính xác khi bạn định nghĩa kiểu cho chúng khi bạn tạo một thể hiện. Phương thức getItem trong ví dụ mã này sẽ tạo ra một số.

5. Ràng buộc chung

Bạn có thể sử dụng ràng buộc chung để hạn chế các kiểu mà một ràng buộc chung có thể chấp nhận, đảm bảo chúng có các thuộc tính cụ thể.

Ví dụ, nếu bạn có một hàm cần truy cập thuộc tính length của đầu vào, bạn có thể sử dụng ràng buộc để đảm bảo chỉ các kiểu có thuộc tính length mới được phép. Điều này ngăn Typescript đưa ra lỗi hoặc để các kiểu không tương thích lọt qua.

function printLength<T>(value: T): void { console.log(value.length); // Error: Typescript doesn’t know if the value has length } 

Ở đây, giá trị T không được xác định bằng thuộc tính length . Để bỏ qua vấn đề này, chúng ta có thể thêm một ràng buộc chỉ định rằng T phải có thuộc tính length . Chúng ta thực hiện điều này bằng cách nói T extends { length: number } .

function printLength<T extends { length: number}>(value: T): void { console.log(value.length); // Now Typescript knows length exists
} printLength("hello"); // Output: 5
printLength([1, 2, 3]); // Output: 3
printLength({ length: 10 }); // Output: 10

Bây giờ, hàm này sẽ có thuộc tính độ dài; nó sẽ không đưa ra bất kỳ lỗi nào và sẽ thực thi với độ dài của đầu vào.

Phần kết luận

Các generic Typescript cho phép bạn viết mã linh hoạt, có thể tái chế và an toàn về kiểu. Bạn có thể quản lý nhiều kiểu dữ liệu mà không cần lặp lại mã bằng cách sử dụng các lớp, phương thức và giao diện với các generic này.

Các ràng buộc chung, nhiều kiểu và các kiểu mặc định là một số trường hợp sử dụng chính mà chúng tôi đã xem xét trong bài đăng này và cho thấy cách mỗi trường hợp có thể cải thiện khả năng mở rộng và khả năng bảo trì của chương trình.

Hiểu về các kiểu generic của Typescript có thể giúp bạn viết mã chính xác hơn, dễ thích ứng hơn và an toàn hơn, giúp ứng dụng Typescript của bạn mạnh mẽ hơn.

Cảm ơn các bạn đã theo dõi!

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giới thiệu Typescript - Sự khác nhau giữa Typescript và Javascript

Typescript là gì. TypeScript là một ngôn ngữ giúp cung cấp quy mô lớn hơn so với JavaScript.

0 0 539

- vừa được xem lúc

Type annotation vs Type Inference - Typescript

Trong bài viết này, chúng ta sẽ tìm hiểu kỹ về TypeScript bằng cách tìm hiểu sự khác biệt giữa kiểu chú thích và kiểu suy luận. Tôi sẽ cho rằng bạn có một số kinh nghiệm về JavaScript và biết về các kiểu cơ bản, như chuỗi, số và boolean.

0 0 55

- vừa được xem lúc

Type Annotation và Type Inference trong TypeScript là gì?

Khi làm việc với javascript chắc hẳn các bạn đã quá quen với việc dùng biến mà không cần phải quan tâm đến kiểu dữ liệu của nó là gì phải không? Đúng là mới đầu tiếp cận với Typescript mình cũng cảm thấy nó khá là phiền vì cần phải khai báo đủ type để nó chặt chẽ hơn. Lúc đó mình còn nghĩ: " JavaScr

0 0 42

- vừa được xem lúc

Tìm hiểu TypeScript và kiến thức cơ bản

TypeScript là gì. TypeScript sử dụng tất cả các tính năng của của ECMAScript 2015 (ES6) như classes, modules.

0 0 58

- vừa được xem lúc

TypeScript - P1: Vì sao TypeScript được yêu thích đến vậy?

Trải nghiệm thực tế. Trước khi là một Web Developer, tôi là một Mobile Developer và Java là thứ mà tôi từng theo đuổi.

0 1 72

- vừa được xem lúc

4 Tính năng rất hay từ TypeScript

Xin chào các bạn hôm nay mình xin chia sẽ những tính năng rất hay của TypeScript (TS), các bạn cùng tìm hiểu nhé. Ngoài việc set Type cho biến, tham số hay function thì ví dụ khi bạn nhìn vào một tham

0 0 98