Giải thích về Generic nâng cao của TypeScript bằng ví dụ

Generic trong TypeScript cung cấp một cách để tạo các thành phần mã có thể tái sử dụng và linh hoạt bằng cách làm việc với nhiều loại dữ liệu khác nhau. Generic nâng cao đưa khái niệm này đi xa hơn bằng cách giới thiệu các tính năng bổ sung như ràng buộc, giá trị mặc định và nhiều loại, cho phép các nhà phát triển viết mã mạnh mẽ hơn và an toàn hơn. Trong bài viết này, các ví dụ sẽ được sử dụng để khám phá các khái niệm nâng cao này trong generic.

Ràng buộc chung

Ràng buộc giới hạn các kiểu mà một generic có thể chấp nhận. Điều này đảm bảo rằng kiểu được truyền cho một hàm hoặc lớp generic đáp ứng các tiêu chí nhất định. Ví dụ, một ràng buộc có thể được sử dụng để đảm bảo rằng kiểu generic có một thuộc tính hoặc phương thức cụ thể.

function getLength<T extends { length: number }>(arg: T): number {
    return arg.length;
}

const stringLength = getLength("TypeScript");
const arrayLength = getLength([1, 2, 3]);

Trong ví dụ này, ràng buộc <T extends { length: number }> đảm bảo rằng đối số được truyền cho getLength có thuộc tính length.

Nhiều loại thuốc gốc

TypeScript cho phép sử dụng nhiều kiểu chung trong cùng một hàm, lớp hoặc giao diện. Điều này hữu ích khi làm việc với các cặp giá trị hoặc các cấu trúc dữ liệu khác liên quan đến nhiều kiểu.

function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second];
}

const stringNumberPair = pair("TypeScript", 2024);

Hàm này, pair, chấp nhận hai kiểu chung khác nhau, TU, và trả về một bộ chứa cả hai kiểu.

Các loại chung mặc định

Generic trong TypeScript cũng có thể có các kiểu mặc định. Điều này hữu ích khi bạn muốn một generic có kiểu dự phòng nếu không có kiểu cụ thể nào được cung cấp.

function identity<T = string>(value: T): T {
    return value;
}

const defaultString = identity("Hello");  // T is string
const customNumber = identity<number>(100);  // T is number

Trong ví dụ này, nếu không có kiểu nào được truyền vào identity, thì mặc định sẽ là string.

Sử dụng Generics với Giao diện

Generic có thể được sử dụng với giao diện để xác định các cấu trúc phức tạp trong đó các kiểu không cố định. Điều này làm tăng tính linh hoạt cho cách quản lý dữ liệu.

interface Container<T> {
    value: T;
}

const stringContainer: Container<string> = { value: "Hello" };
const numberContainer: Container<number> = { value: 42 };

Giao diện Container được thiết kế để lưu trữ giá trị của bất kỳ kiểu nào, cho phép nhiều loại container khác nhau với các kiểu cụ thể.

Các lớp chung

Các lớp trong TypeScript cũng có thể là chung chung. Điều này đặc biệt hữu ích khi thiết kế các lớp hoạt động với nhiều kiểu dữ liệu khác nhau, chẳng hạn như các lớp lưu trữ hoặc thu thập dữ liệu.

class DataStore<T> {
    private data: T[] = [];

    add(item: T): void {
        this.data.push(item);
    }

    getAll(): T[] {
        return this.data;
    }
}

const stringStore = new DataStore<string>();
stringStore.add("Hello");
stringStore.add("TypeScript");

const numberStore = new DataStore<number>();
numberStore.add(42);

Trong ví dụ này, lớp DataStore hoạt động với mọi loại dữ liệu, cung cấp giải pháp an toàn về mặt kiểu để lưu trữ và truy xuất các phần tử.

Phần kết luận

Các generic nâng cao trong TypeScript là một công cụ mạnh mẽ để viết mã linh hoạt, có thể tái sử dụng và an toàn về kiểu. Bằng cách sử dụng các ràng buộc, nhiều kiểu, giá trị mặc định và generic trong các lớp và giao diện, các nhà phát triển có thể viết mã phức tạp và mạnh mẽ hơn. Hiểu và sử dụng các khái niệm nâng cao này cho phép linh hoạt hơn và đảm bảo an toàn về kiểu trên các ứng dụng.