Explore all courses in this category.
Explore all courses in this category.
Nguyễn Đình Cường
0 comment
09 Nov, 2026
Khi đã quen với kiểu cơ bản của TypeScript, hai câu hỏi xuất hiện gần như chắc chắn: nên dùng interface hay type, và generics là gì mà ai cũng nhắc đến. Bài này trả lời cả hai bằng ví dụ ngắn gọn.
Cả hai đều mô tả hình dạng của dữ liệu:
interface NguoiDung {
id: number;
ten: string;
}
type NguoiDung2 = {
id: number;
ten: string;
};Với object đơn giản, chúng gần như hoán đổi được cho nhau. Khác biệt đáng biết:
extends, type dùng phép giao &.interface Admin extends NguoiDung {
quyen: string[];
}
type Id = number | string; // union, chỉ dùng được type
type Cap = [number, number]; // tuple
type QuanTri = NguoiDung2 & { quyen: string[] };Một quy ước thực dụng: dùng interface cho hình dạng của object và API công khai, dùng type cho union, tuple hay kiểu phức hợp. Điều quan trọng hơn là cả nhóm thống nhất một cách.
Union type cho phép giá trị thuộc một trong nhiều kiểu. Khi dùng, bạn thu hẹp (narrow) để TypeScript biết đang ở nhánh nào:
type KetQua =
| { ok: true; du_lieu: string }
| { ok: false; loi: string };
function xuLy(kq: KetQua) {
if (kq.ok) {
console.log(kq.du_lieu); // TypeScript biết có du_lieu
} else {
console.log(kq.loi); // và ở đây có loi
}
}Mẫu này gọi là discriminated union, rất hữu ích để mô hình hóa trạng thái như đang tải, thành công, lỗi mà không có tổ hợp vô lý.
Hãy xem hàm trả về phần tử đầu tiên của mảng. Nếu ghi any, bạn mất thông tin kiểu. Generics giữ lại nó:
function dau<T>(mang: T[]): T | undefined {
return mang[0];
}
const a = dau([1, 2, 3]); // a: number | undefined
const b = dau(['x', 'y']); // b: string | undefinedT là tham số kiểu, được suy ra từ đối số truyền vào. Bạn có thể coi nó là "biến dành cho kiểu". Với generics, một hàm viết một lần dùng được cho mọi kiểu mà vẫn an toàn.
interface PhanHoiApi<T> {
du_lieu: T;
trangThai: number;
}
interface Bai { id: number; tieuDe: string }
const r: PhanHoiApi<Bai[]> = { du_lieu: [], trangThai: 200 };
// ràng buộc: T phải có thuộc tính id
function timTheoId<T extends { id: number }>(ds: T[], id: number): T | undefined {
return ds.find(x => x.id === id);
}Từ khóa extends trong ngữ cảnh này nghĩa là "phải tối thiểu có hình dạng này". Nhờ vậy bên trong hàm bạn truy cập được x.id một cách an toàn.
TypeScript có sẵn các kiểu biến đổi giúp bạn khỏi viết lại:
interface Bai {
id: number;
tieuDe: string;
noiDung: string;
}
type BanNhap = Partial<Bai>; // mọi trường thành tùy chọn
type ChiDoc = Readonly<Bai>; // không sửa được
type TomTat = Pick<Bai, 'id' | 'tieuDe'>; // chọn một số trường
type KhongId = Omit<Bai, 'id'>; // bỏ một trường
type TuDien = Record<string, number>; // object khóa chuỗi, giá trị sốVí dụ khi tạo bài mới, dữ liệu gửi lên chưa có id nên dùng Omit<Bai, 'id'>, còn khi cập nhật từng phần dùng Partial.
any để "cho nhanh", mất hết lợi ích của kiểu.as để làm im lỗi thay vì sửa nguyên nhân. Ép kiểu chỉ nên dùng khi bạn biết điều trình biên dịch không biết.Ghép các kiến thức thành một hàm lấy dữ liệu có kiểu rõ ràng:
interface Khoa { id: number; ten: string; gia: number }
async function layJson<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json() as Promise<T>;
}
const ds = await layJson<Khoa[]>('/api/khoa-hoc');
console.log(ds[0].ten); // có gợi ý mã đầy đủMột hàm generic duy nhất phục vụ mọi loại dữ liệu, và nơi gọi nói rõ mình mong đợi kiểu gì.
Dùng interface cho hình dạng object, type cho union và kiểu phức hợp. Hiểu generics như "kiểu có tham số", dùng extends để ràng buộc và tận dụng utility types. Bạn sẽ thấy mã vừa an toàn vừa gọn.
Nếu bạn muốn luyện TypeScript trong các dự án React và Node thực tế, hãy xem qua khóa học trên codebem.com.
Nguyễn Đình Cường
0 comment