Generics trong TypeScript | TypeScript Series
1. Generics là gì?
Generics (kiểu tổng quát) giúp bạn viết code có thể dùng cho nhiều kiểu dữ liệu khác nhau, nhưng vẫn giữ được an toàn kiểu (type safety).
Thay vì viết 3 hàm khác nhau cho number, string, và boolean, bạn chỉ cần một hàm “generic” có thể tự thích nghi với kiểu bạn truyền vào.

Ví dụ:
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("hello"); // output1 là string
let output2 = identity<number>(42); // output2 là number
Tlà type variable – biến đại diện cho một kiểu dữ liệu.- Khi gọi hàm, bạn có thể chỉ rõ kiểu (
<string>), hoặc để TypeScript tự đoán (identity("hello")).
2. Generic Function (Hàm tổng quát)
Ví dụ từ W3Schools:
function createPair<S, T>(v1: S, v2: T): [S, T] {
return [v1, v2];
}
console.log(createPair<string, number>('hello', 42)); // ['hello', 42]
Giải thích:
SvàTlà hai type variable (bạn có thể đặt tên khác như<A, B>).v1: Svàv2: Tlà tham số có kiểu tương ứng.- Kết quả trả về
[S, T]— một tuple chứa hai giá trị kiểu S và T. - Khi gọi, bạn truyền
stringvànumber, nên TypeScript hiểu rõ kiểu của từng phần tử.
💡 Lợi ích:
Một hàm createPair có thể dùng cho bất kỳ kiểu dữ liệu nào, không cần viết lại.
3. Generic Class (Lớp tổng quát)
Ví dụ:
class NamedValue<T> {
private _value: T | undefined;
constructor(private name: string) {}
public setValue(value: T) {
this._value = value;
}
public getValue(): T | undefined {
return this._value;
}
public toString(): string {
return `${this.name}: ${this._value}`;
}
}
let value = new NamedValue<number>('myNumber');
value.setValue(10);
console.log(value.toString()); // myNumber: 10
Giải thích:
class NamedValue<T>có một biến kiểu T.- Khi tạo instance
new NamedValue<number>, ta nói rằng T = number. - Nhờ vậy,
setValuechỉ nhậnnumber,getValuetrả vềnumber.
💡 Tương tự, bạn có thể tạo NamedValue<string>, NamedValue<Date>, v.v.
4. Generic Type (Kiểu tổng quát)
Bạn có thể tạo type hoặc interface dùng generic:
type Wrapped<T> = { value: T };
const wrappedNumber: Wrapped<number> = { value: 10 };
const wrappedString: Wrapped<string> = { value: "Hello" };
Điều này giúp định nghĩa những “container” kiểu dữ liệu có thể chứa bất kỳ kiểu nào.
5. Default Type & Constraints (Kiểu mặc định và giới hạn kiểu)
Kiểu mặc định:
Nếu bạn không chỉ rõ kiểu, có thể đặt mặc định cho generic:
class NamedValue<T = string> {
constructor(private name: string, private value?: T) {}
}
let myValue = new NamedValue('example');
→ Ở đây T mặc định là string.
Giới hạn kiểu (Constraints):
Bạn có thể giới hạn kiểu bằng extends:
function createLoggedPair<S extends string | number, T extends string | number>(
v1: S, v2: T
): [S, T] {
console.log(`creating pair: ${v1}, ${v2}`);
return [v1, v2];
}
→ S và T chỉ có thể là string hoặc number, không thể là kiểu khác.
6. Lợi ích của Generics
- Tái sử dụng code: Một hàm/lớp có thể hoạt động với nhiều kiểu khác nhau.
- Giữ an toàn kiểu: TypeScript kiểm tra và đảm bảo đúng kiểu.
- Giúp code rõ ràng hơn: Khi thấy
Box<T>, bạn biết nó là container chứa kiểu T.
7. Một số ví dụ dễ hiểu hơn
Hàm generic:
function getFirstElement<T>(arr: T[]): T {
return arr[0];
}
const num = getFirstElement<number>([1, 2, 3]);
const str = getFirstElement<string>(['a', 'b', 'c']);
Lớp generic:
class Box<T> {
constructor(private content: T) {}
getContent(): T {
return this.content;
}
}
const stringBox = new Box('hello');
const dateBox = new Box(new Date());
Constraint (giới hạn):
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): void {
console.log(item.length);
}
logLength('hello'); // OK
logLength([1, 2, 3]); // OK
// logLength(42); ❌ lỗi - number không có length
Tóm tắt
| Khái niệm | Mô tả ngắn | Ví dụ |
|---|---|---|
| Generic Function | Hàm có thể nhận nhiều kiểu khác nhau | function add<T>(a: T): T |
| Generic Class | Lớp hoạt động với nhiều kiểu | class Box<T> |
| Generic Type | Kiểu tổng quát | type Wrapper<T> = { value: T } |
| Constraint | Giới hạn kiểu hợp lệ | <T extends string> |
| Default Type | Kiểu mặc định khi không chỉ rõ | <T = string> |
All rights reserved