Bài 8. Kiểu nâng cao

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.

50 phútNâng cao
Bạn sẽ học được
  • 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
Thuật ngữ cần biết
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"); // string

Mapped 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ày

Template 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";
Mẹo hay

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ã.

Lưu ý

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 đủ.

Thực hành
  1. Viết BatBuoc<T> bỏ dấu ? của mọi thuộc tính (gợi ý: -?).
  2. Viết ThamSoDau<F> lấy kiểu tham số đầu tiên của hàm bằng infer.
  3. Từ đối tượng cấu hình, tạo kiểu các khóa có giá trị là số.
  4. Tạo kiểu tên sự kiện "user:created" | "user:deleted" bằng template literal.
Tóm tắt bài học
  • 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.