TYPESCRIPT 2.X CĂN BẢN
Hàm trong TypeScript Sử dụng hàm generics Sử dụng hàm generics trong TypeScript Sử dụng Array trong TypeScript Sử dụng Tuple trong TypeScript Sử dụng kiểu Array, Tuple, Enum trong TypeScript Interface trong Typescript Hybrid Types với Interface trong TypeScript Khác biệt khi sử dụng 'type' và 'interface' trong TypeScript Cách dùng Enum trong TypeScript Types và interface trong TypeScript Class trong TypeScript Sử dụng Abstract Classes trong TypeScript Sử dụng Interface trong Typescript (phần 2) Sử dụng Casting trong TypeScript Tìm hiểu về Generics trong TypeScript Tìm hiểu về utility types trong Typescript Utility types phổ biến trong Typescript Sử dụng default parameters trong TypeScript Modules trong TypeScript Rest Parameters trong TypeScript Sử dụng Function Overloadings trong TypeScript Sử dụng Type Assertions trong TypeScript Sử dụng Static Methods và Properties trong TypeScript. Sử dụng Access Modifiers trong TypeScript 10 mẹo và thủ thuật hay nhất trong TypeScript 5 Cách giúp bạn loại bỏ "any" trong TypeScript Mapped Types trong TypeScript Dependency Injection trong Typescript 5 ký hiệu khó hiểu cần biết trong TypeScript Cách sử dụng @ts-expect-error trong Typescript Declaration Merging trong TypeScript Tìm hiểu Branded Type trong TypeScript Namespaces trong Typescript Phân biệt Modules và Namespaces trong TypeScript Triple-Slash Directives trong TypeScript là gì? Phần 1: Biến trong TypeScript Phần 2: Biến trong TypeScript Property trong TypeScript Phân tích JSON trong TypeScript Bài tập TypeScript: Các dạng bài tập cơ bản Bài tập TypeScript: Lập trình Hướng đối tượng trong TypeScript Bài tập TypeScript: Kiểu Generic Types trong Typescript Bài tập TypeScript: Các kiểu dữ liệu mảng và tuple. Bài tập TypeScript: Classes và Inheritance trong Typescript Bài 01: TypeScript là gì? TypeScript và Javascript Bài 02: Cài đặt TypeScript Bài 03: TypeScript Basic Types Bài 04: Khai báo biến trong TypeScript Bài 05: Lệnh If Else và Switch Case trong TypeScript Bài 06: Vòng lặp trong TypeScript
CÁC CHỦ ĐỀ
BÀI MỚI NHẤT
MỚI CẬP NHẬT

Tìm hiểu về Generics trong TypeScript

Trong bài viết này là mình giúp bạn hiểu rõ về khái niệm Generics trong TypeScript và cách sử dụng chúng trong việc tạo mã nguồn linh hoạt và tái sử dụng. Mình sẽ tìm hiểu về cú pháp cơ bản của Generics, cách áp dụng chúng cho hàm, lớp, cũng như sử dụng trong các interface và class. Bài viết cũng sẽ đề cập đến việc giới hạn kiểu dữ liệu bằng Generics và cách rút trích kiểu dữ liệu tự động.

test php

banquyen png
Bài viết này được đăng tại freetuts.net, không được copy dưới mọi hình thức.

Cuối cùng, mình sẽ xem xét lợi ích và hạn chế của việc sử dụng Generics, cùng với các ví dụ minh họa thực tế để củng cố kiến thức. Hãy cùng tìm hiểu ngay bài viết cùng mình nhé!

Generics là gì?

Generics là một tính năng trong TypeScript (và cũng xuất hiện trong nhiều ngôn ngữ lập trình khác) cho phép bạn làm cho kiểu dữ liệu của một biến, hàm, lớp hoặc interface trở nên động, tức là kiểu dữ liệu có thể được thay đổi tùy theo cách mà bạn sử dụng nó. Generics giúp bạn viết mã linh hoạt và tái sử dụng hơn bằng cách cho phép bạn xác định kiểu dữ liệu một cách động, chẳng hạn kiểu dữ liệu của một biến hoặc đối số của một hàm.

Trong TypeScript, bạn có thể sử dụng Generics bằng cách sử dụng các tham số kiểu (type parameters) khi định nghĩa hàm, lớp, interface, hoặc kiểu dữ liệu. Tham số kiểu này sẽ là kiểu dữ liệu tùy chỉnh, cho phép bạn truyền kiểu dữ liệu cụ thể khi bạn sử dụng thực thể (instance) của hàm, lớp, hoặc interface đó.

Bài viết này được đăng tại [free tuts .net]

Ví dụ đơn giản về Generics trong TypeScript:

function identity<T>(arg: T): T {
    return arg;
}

let output = identity<string>("Hello, Generics!");

Trong ví dụ trên, hàm identity sử dụng tham số kiểu T, cho phép bạn truyền vào nó một kiểu dữ liệu cụ thể khi gọi hàm. Generics giúp hàm identity trả về cùng kiểu dữ liệu mà bạn truyền vào.

Lợi ích của Generics

Generics cung cấp nhiều lợi ích quan trọng cho TypeScript:

  • Tái sử dụng mã nguồn: Generics giúp bạn viết mã nguồn một lần và tái sử dụng nó với nhiều kiểu dữ liệu khác nhau. Điều này giúp giảm mã trùng lặp.

  • Kiểm tra kiểu tại thời gian biên dịch: TypeScript kiểm tra kiểu dữ liệu tại thời gian biên dịch, giúp tránh lỗi thời gian chạy do kiểu dữ liệu không phù hợp.

  • Tích hợp với các thư viện bên ngoài: Generics cho phép bạn tương tác với các thư viện bên ngoài và xác định kiểu dữ liệu một cách dễ dàng, giúp giảm nguy cơ lỗi.

Hạn chế và khi nào nên tránh sử dụng

Mặc dù Generics là một công cụ mạnh, nhưng cũng có những hạn chế và trường hợp nên tránh sử dụng:

  • Phức tạp quá mức: Sử dụng Generics không cần thiết có thể làm cho mã nguồn trở nên phức tạp và khó hiểu.

  • Overusing Generics: Việc sử dụng Generics cho mọi thứ có thể gây hiệu suất yếu và làm cho mã nguồn trở nên khó quản lý. Hãy sử dụng Generics khi thực sự cần thiết.

  • Sự hiểu biết về kiểu dữ liệu: Generics yêu cầu bạn hiểu biết về kiểu dữ liệu và cách chúng hoạt động. Nếu không hiểu rõ, có thể dẫn đến lỗi logic.

  • Sự phụ thuộc vào API bên ngoài: Sử dụng Generics để tạo sự phụ thuộc vào API bên ngoài có thể tạo ra các ràng buộc không cần thiết và làm cho mã nguồn dễ bị thay đổi.

Cú pháp và sử dụng cơ bản

Sử dụng Generics cho hàm và lớp

Generics có thể được sử dụng cho hàm và lớp trong TypeScript. Dưới đây là cú pháp cơ bản cho cả hai trường hợp:

Generics cho hàm:

function doSomething<T>(arg: T): T {
    // Các xử lý với biến arg
    return arg;
}

let result = doSomething<string>("Hello, Generics!");

Trong ví dụ trên, hàm doSomething nhận một tham số kiểu T và trả về cùng kiểu dữ liệu.

Generics cho lớp:

class GenericBox<T> {
    private value: T;

    constructor(initialValue: T) {
        this.value = initialValue;
    }

    getValue(): T {
        return this.value;
    }
}

let numberBox = new GenericBox<number>(42);
let stringBox = new GenericBox<string>("Hello, Generics!");

console.log(numberBox.getValue()); // 42
console.log(stringBox.getValue()); // "Hello, Generics!"

Trong ví dụ về lớp GenericBox, kiểu T là kiểu dữ liệu của value, và bạn có thể tạo các thực thể của lớp với các kiểu dữ liệu khác nhau.

Ví dụ minh họa

Một ví dụ cụ thể về việc sử dụng Generics cho hàm:

function firstElement<T>(arr: T[]): T | undefined {
    if (arr.length === 0) {
        return undefined;
    }
    return arr[0];
}

const numbers = [1, 2, 3, 4, 5];
const firstNum = firstElement(numbers); // firstNum có kiểu number

const names = ["Alice", "Bob", "Charlie"];
const firstName = firstElement(names); // firstName có kiểu string

Hàm firstElement sử dụng Generics để trả về phần tử đầu tiên của một mảng bất kỳ, bất kể kiểu dữ liệu của mảng đó.

Parameter Types và Return Types

Sử dụng Generics cho Parameter Types

Generics cho parameter types cho phép bạn chỉ định kiểu dữ liệu của tham số đầu vào cho hàm, và kiểu dữ liệu này có thể thay đổi động dựa trên cách bạn gọi hàm.

function combine<T>(input1: T, input2: T): T {
    return input1 + input2;
}

const result = combine<number>(2, 3); // Kết quả có kiểu number

Trong ví dụ trên, hàm combine nhận hai tham số kiểu T, và kiểu của kết quả sẽ là cùng kiểu T. Bằng cách sử dụng Generics, bạn có thể chỉ định kiểu dữ liệu của result khi gọi hàm.

Sử dụng Generics cho Return Types

Bạn cũng có thể sử dụng Generics để định nghĩa kiểu trả về của một hàm.

function createArray<T>(value: T, times: number): T[] {
    const result: T[] = [];
    for (let i = 0; i < times; i++) {
        result.push(value);
    }
    return result;
}

const newArray = createArray<string>("Hello", 3); // newArray có kiểu string[]

Trong ví dụ này, hàm createArray trả về một mảng kiểu T, và bạn có thể chỉ định kiểu trả về khi gọi hàm.

Ví dụ minh họa

Một ví dụ cụ thể về việc sử dụng Generics cho parameter types và return types:

function mergeArrays<T>(arr1: T[], arr2: T[]): T[] {
    return arr1.concat(arr2);
}

const numArray1 = [1, 2, 3];
const numArray2 = [4, 5, 6];
const mergedNumArray = mergeArrays<number>(numArray1, numArray2); // mergedNumArray có kiểu number[]

Giới hạn (Constraints) trong Generics

Giới hạn trong Generics cho phép bạn xác định rõ hơn kiểu dữ liệu cho tham số generics bằng cách áp dụng một số ràng buộc.

Sử dụng giới hạn để hạn chế kiểu dữ liệu

Giới hạn trong Generics có thể được sử dụng để hạn chế kiểu dữ liệu cho tham số. Ví dụ, nếu bạn muốn chỉ cho phép sử dụng Generics với kiểu dữ liệu là các lớp có một phương thức cụ thể, bạn có thể sử dụng giới hạn như sau:

interface Shape {
    calculateArea(): number;
}

function calculateTotalArea<T extends Shape>(shapes: T[]): number {
    let totalArea = 0;
    shapes.forEach(shape => {
        totalArea += shape.calculateArea();
    });
    return totalArea;
}

class Circle implements Shape {
    constructor(public radius: number) {}
    calculateArea() {
        return Math.PI * this.radius * this.radius;
    }
}

class Rectangle implements Shape {
    constructor(public width: number, public height: number) {}
    calculateArea() {
        return this.width * this.height;
    }
}

const shapes: Shape[] = [new Circle(5), new Rectangle(4, 6)];
const totalArea = calculateTotalArea(shapes); // Đúng

Trong ví dụ này,mình sử dụng giới hạn <T extends Shape> để đảm bảo rằng T phải là một lớp thỏa mãn giao diện Shape.

Ví dụ minh họa

Một ví dụ cụ thể về việc sử dụng giới hạn trong Generics:

interface Lengthwise {
    length: number;
}

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

const result1 = getArrayLength("Hello"); // result1 có kiểu number
const result2 = getArrayLength([1, 2, 3, 4, 5]); // result2 có kiểu number

Trong ví dụ này, mình sử dụng giới hạn để đảm bảo rằng tham số arg phải có thuộc tính length kiểu number.

Generics trong Interface và Class

Sử dụng Generics trong Interface

Generics có thể được sử dụng trong interface để xây dựng các giao diện có thể làm việc với nhiều kiểu dữ liệu khác nhau. Ví dụ:

interface Pair<T, U> {
    first: T;
    second: U;
}

const pair1: Pair<number, string> = { first: 1, second: "two" };
const pair2: Pair<string, boolean> = { first: "hello", second: true };

Trong ví dụ này, mình đã xây dựng một giao diện Pair sử dụng Generics để cho phép định nghĩa kiểu dữ liệu cho first second.

Sử dụng Generics trong Class

Generics cũng có thể được sử dụng trong class để tạo các lớp có thể làm việc với nhiều kiểu dữ liệu khác nhau. Ví dụ:

class Box<T> {
    private content: T;

    constructor(value: T) {
        this.content = value;
    }

    getContent(): T {
        return this.content;
    }
}

const numberBox = new Box<number>(42);
const stringBox = new Box<string>("Hello, Generics!");

const numberContent = numberBox.getContent(); // numberContent có kiểu number
const stringContent = stringBox.getContent(); // stringContent có kiểu string

Trong ví dụ này, mình đã tạo một class Box sử dụng Generics để cho phép định nghĩa kiểu dữ liệu cho nội dung trong hộp.

Ví dụ minh họa

Một ví dụ minh họa về việc sử dụng Generics trong interface và class:

interface KeyValuePair<K, V> {
    key: K;
    value: V;
}

class Dictionary<K, V> {
    private items: KeyValuePair<K, V>[] = [];

    add(key: K, value: V) {
        this.items.push({ key, value });
    }

    getValue(key: K): V | undefined {
        const pair = this.items.find(item => item.key === key);
        return pair ? pair.value : undefined;
    }
}

const numberDict = new Dictionary<number, string>();
numberDict.add(1, "one");
numberDict.add(2, "two");

const wordDict = new Dictionary<string, number>();
wordDict.add("three", 3);
wordDict.add("four", 4);

const numberValue = numberDict.getValue(1); // numberValue có kiểu string
const wordValue = wordDict.getValue("three"); // wordValue có kiểu number

Trong ví dụ này,mình đã sử dụng Generics trong interface KeyValuePair và class Dictionary để tạo một từ điển cho các kiểu dữ liệu khác nhau.

Inference (Rút trích) kiểu dữ liệu

Rút trích kiểu dữ liệu tự động

Một trong những tính năng mạnh mẽ của TypeScript là khả năng tự động rút trích kiểu dữ liệu. Điều này có nghĩa rằng TypeScript có thể suy luận và gán kiểu dữ liệu cho biến dựa trên giá trị mà bạn gán cho nó. Ví dụ:

const numberValue = 42; // TypeScript tự động rút trích kiểu number cho numberValue
const stringValue = "Hello, Inference!"; // TypeScript tự động rút trích kiểu string cho stringValue

Sử dụng inference để làm cho mã nguồn ngắn gọn hơn

Khi bạn sử dụng Generics, inference giúp bạn viết mã nguồn ngắn gọn hơn bằng cách tự động xác định kiểu dữ liệu dựa trên tham số bạn truyền vào hàm hoặc lớp Generic. Ví dụ:

function identity<T>(arg: T): T {
    return arg;
}

const num = identity(42); // TypeScript tự động rút trích kiểu number cho num
const str = identity("TypeScript"); // TypeScript tự động rút trích kiểu string cho str

Ví dụ minh họa

Một ví dụ minh họa về việc sử dụng inference:

function multiply(a: number, b: number) {
    return a * b;
}

const result = multiply(5, 6); // TypeScript tự động rút trích kiểu number cho result

Trong ví dụ này, TypeScript tự động rút trích kiểu number cho biến result dựa trên kết quả của hàm multiply.

Ví dụ minh họa

Sử dụng Generics để tái sử dụng logic

Ví dụ về sử dụng Generics để tái sử dụng logic trong hàm đảo ngược mảng:

function reverse<T>(arr: T[]): T[] {
    return arr.reverse();
}

const numbers = [1, 2, 3];
const reversedNumbers = reverse(numbers); // reversedNumbers có kiểu number[]

Sử dụng Generics trong các tình huống phức tạp

Generics có thể được sử dụng trong nhiều tình huống phức tạp như xây dựng thư viện, quản lý trạng thái ứng dụng, và xử lý dữ liệu động. Việc này giúp mã nguồn dễ dàng mở rộng và duyệt qua các kiểu dữ liệu khác nhau mà không cần viết mã nguồn gần như giống nhau.

Kết quả

Trong bài viết này, mình đã tìm hiểu về Generics trong TypeScript, một tính năng mạnh mẽ cho phép ta làm việc với các kiểu dữ liệu động và tạo mã nguồn dễ tái sử dụng. Generics cho phép ta xác định kiểu dữ liệu linh hoạt cho biến, hàm, lớp và giao diện. Mình đã thấy cách sử dụng Generics để giải quyết nhiều tình huống thực tế và tận dụng lợi ích của việc kiểm tra kiểu tại thời gian biên dịch.

Tuy Generics là một công cụ mạnh mẽ, nhưng cũng cần hiểu cách sử dụng chúng một cách hợp lý. Nếu không sử dụng đúng cách, Generics có thể làm cho mã nguồn trở nên phức tạp và khó quản lý. Hãy sử dụng Generics khi thực sự cần thiết và khi muốn tạo sự linh hoạt và tái sử dụng trong mã nguồn TypeScript của bạn.

Mình đã thấy Generics có thể được sử dụng trong nhiều tình huống, từ xử lý dữ liệu động đến quản lý trạng thái ứng dụng và xây dựng thư viện. Nhờ vào tính linh hoạt của Generics, mình có thể mở rộng ứng dụng và duyệt qua các kiểu dữ liệu khác nhau mà không cần viết mã nguồn gần như giống nhau.

Hy vọng rằng bài viết này đã giúp bạn hiểu rõ hơn về Generics trong TypeScript và cách áp dụng chúng vào công việc lập trình hàng ngày.

Cùng chuyên mục:

Bài tập TypeScript: Classes và Inheritance trong Typescript

Bài tập TypeScript: Classes và Inheritance trong Typescript

Bài tập TypeScript: Các kiểu dữ liệu mảng và tuple.

Bài tập TypeScript: Các kiểu dữ liệu mảng và tuple.

Bài tập TypeScript: Kiểu Generic Types trong Typescript

Bài tập TypeScript: Kiểu Generic Types trong Typescript

Bài tập TypeScript: Lập trình Hướng đối tượng trong TypeScript

Bài tập TypeScript: Lập trình Hướng đối tượng trong TypeScript

Bài tập TypeScript: Các dạng bài tập cơ bản

Bài tập TypeScript: Các dạng bài tập cơ bản

Phân tích JSON trong TypeScript

Phân tích JSON trong TypeScript

Property trong TypeScript

Property trong TypeScript

Phần 2: Biến trong TypeScript

Phần 2: Biến trong TypeScript

Phần 1: Biến trong TypeScript

Phần 1: Biến trong TypeScript

Triple-Slash Directives trong TypeScript là gì?

Triple-Slash Directives trong TypeScript là gì?

Phân biệt Modules và Namespaces trong TypeScript

Phân biệt Modules và Namespaces trong TypeScript

Tìm hiểu Branded Type trong TypeScript

Tìm hiểu Branded Type trong TypeScript

Namespaces trong Typescript

Namespaces trong Typescript

Declaration Merging trong TypeScript

Declaration Merging trong TypeScript

Cách sử dụng @ts-expect-error trong typescript

Cách sử dụng @ts-expect-error trong typescript

5 ký hiệu khó hiểu cần biết trong TypeScript

5 ký hiệu khó hiểu cần biết trong TypeScript

Dependency Injection trong Typescript

Dependency Injection trong Typescript

Mapped Types trong TypeScript

Mapped Types trong TypeScript

5 Cách giúp bạn loại bỏ

5 Cách giúp bạn loại bỏ "any" trong TypeScript

10 mẹo và thủ thuật hay nhất trong TypeScript

10 mẹo và thủ thuật hay nhất trong TypeScript

Top