Bài này giới thiệu “lập trình ở tầng kiểu”: tạo kiểu mới từ kiểu có sẵn bằng keyof, typeof, indexed access, mapped types, conditional types và template literal types.
- Dùng keyof, typeof, indexed access
- Tự viết mapped type như Partial, Readonly
- Hiểu conditional type và infer
- Tạo kiểu chuỗi bằng template literal type
- keyof
- Union các khóa của một kiểu.
- Mapped type
- Duyệt qua các khóa để tạo kiểu mới.
- Conditional type
A extends B ? X : Yở tầng kiểu.- infer
- Trích một phần kiểu trong conditional type.
Khái niệm và lý thuyết
Thay vì viết lại nhiều kiểu gần giống nhau, ta biến đổi một kiểu gốc. Khi kiểu gốc thay đổi, mọi kiểu dẫn xuất tự cập nhật: đây là nguyên tắc “một nguồn sự thật” (single source of truth) áp dụng cho kiểu.
Các utility type có sẵn như Partial, Pick, Omit, ReturnType đều được xây dựng từ các kỹ thuật trong bài này. Hiểu chúng giúp bạn đọc được thông báo lỗi và định nghĩa kiểu của thư viện.
keyof, typeof, indexed access
const cauHinh = { port: 3000, host: "localhost", debug: true };
type CauHinh = typeof cauHinh; // lấy kiểu từ giá trị
type Khoa = keyof CauHinh; // "port" | "host" | "debug"
type KieuPort = CauHinh["port"]; // number
function lay<T, K extends keyof T>(o: T, k: K): T[K] {
return o[k];
}
lay(cauHinh, "host"); // stringMapped types
type TuyChon<T> = { [K in keyof T]?: T[K] }; // như Partial
type ChiDoc<T> = { readonly [K in keyof T]: T[K] }; // như Readonly
type GetterCua<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
type G = GetterCua<{ ten: string }>; // { getTen: () => string }Conditional types và infer
type LaMang<T> = T extends unknown[] ? true : false;
type A = LaMang<number[]>; // true
type PhanTu<T> = T extends (infer U)[] ? U : T;
type B = PhanTu<string[]>; // string
type GiaTriPromise<T> = T extends Promise<infer V> ? V : T;
// TS có sẵn Awaited<T> làm việc nàyTemplate literal types
type SuKien = "click" | "focus";
type TenXuLy = `on${Capitalize<SuKien>}`; // "onClick" | "onFocus"
type DuongDan = `/api/${"users" | "posts"}/${number}`;
const u: DuongDan = "/api/users/42";Nếu kiểu phức tạp quá ba tầng lồng nhau, hãy tách thành nhiều kiểu trung gian có tên rõ nghĩa. Kiểu cũng cần dễ đọc như mã.
Kiểu đệ quy quá sâu có thể làm trình soạn thảo chậm hoặc báo lỗi “Type instantiation is excessively deep”. Giữ kiểu ở mức vừa đủ.
- Viết
BatBuoc<T>bỏ dấu ? của mọi thuộc tính (gợi ý:-?). - Viết
ThamSoDau<F>lấy kiểu tham số đầu tiên của hàm bằng infer. - Từ đối tượng cấu hình, tạo kiểu các khóa có giá trị là số.
- Tạo kiểu tên sự kiện
"user:created" | "user:deleted"bằng template literal.
- typeof/keyof/T[K] lấy thông tin từ kiểu và giá trị.
- Mapped type biến đổi từng thuộc tính.
- Conditional type + infer trích xuất kiểu.
- Template literal type mô tả chuỗi có cấu trúc.