- Viết generic function với 1, 2, nhiều type parameter, hiểu cơ chế type inference.
- Dùng
extendsđể constraint type parameter; kết hợp vớikeyof. - Đặt default type parameter để API gọn nhưng vẫn linh hoạt.
- Định nghĩa generic class, interface, type alias.
- Master 11 utility types:
Partial,Required,Readonly,Pick,Omit,Record,Exclude,Extract,NonNullable,ReturnType,Parameters,Awaited. - Bắt đầu tự viết utility (
Nullable,DeepPartialstub) — cầu nối sang chương 11.
1. Generic function — type là tham số
Bắt đầu bằng một function "không generic" để thấy vấn đề. Muốn viết hàm identity trả
chính giá trị truyền vào, làm việc với mọi type:
// Cách 1 — quá hẹp, mỗi type 1 hàm
function identityNumber(x: number): number { return x; }
function identityString(x: string): string { return x; }
// Cách 2 — any, mất type
function identity(x: any): any { return x; }
const r = identity(5); // r: any — không gợi ý gì cả
r.toUpperCase(); // 💥 runtime error, TS không catch
Giải pháp: thêm type parameter T trong cặp <...>. T
như một biến type, được "điền" khi gọi hàm:
function identity<T>(x: T): T {
return x;
}
// Cách 1 — gọi explicit, chỉ định T
const a = identity<number>(5); // a: number
const b = identity<string>("hi"); // b: string
// Cách 2 — để TS infer (thường dùng)
const c = identity(5); // c: number — T inferred = number
const d = identity("hi"); // d: string
const e = identity([1, 2, 3]); // e: number[]
Hình dung TypeScript có 2 tầng: tầng value (giá trị runtime — chạy bởi JS) và tầng type (biến mất khi compile). Function thường nhận value và trả value. Generic function còn nhận type và trả type.
identity<T>(x: T): T đọc là: "cho tôi type T, tôi trả function nhận T trả T".
Khi bạn viết identity(5), compiler chạy thuật toán inference: "x là number,
mà x: T → T = number → hàm trả number".
2. Nhiều type parameter
Một generic có thể nhận nhiều type parameter, đặt cách nhau bằng dấu phẩy trong <...>.
Quy ước đặt tên: T, U, V cho generic chung; K cho key,
V cho value, E cho element, R cho return.
function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}
const p1 = pair(1, "x"); // p1: [number, string]
const p2 = pair(true, [1, 2]); // p2: [boolean, number[]]
// Ví dụ thực tế — zip 2 array
function zip<A, B>(as: A[], bs: B[]): [A, B][] {
const n = Math.min(as.length, bs.length);
const out: [A, B][] = [];
for (let i = 0; i < n; i++) out.push([as[i], bs[i]]);
return out;
}
const z = zip([1, 2], ["a", "b"]); // z: [number, string][]
Một ví dụ rất hay trong thực tế — map tự viết, biến mảng T[] thành mảng U[]:
function map<T, U>(arr: T[], fn: (x: T) => U): U[] {
const out: U[] = [];
for (const x of arr) out.push(fn(x));
return out;
}
const lengths = map(["hi", "world"], s => s.length);
// lengths: number[] ← TS infer T=string, U=number
3. Constraint — bó hẹp type parameter với extends
Bình thường T là "bất cứ type nào". Nhưng đôi khi ta cần đảm bảo T có vài property.
Dùng T extends SomeType:
// Không constraint — sai vì không phải mọi T có .length
function len<T>(x: T): number {
return x.length; // ❌ Property 'length' does not exist on type 'T'
}
// Có constraint — T phải có .length
function len<T extends { length: number }>(x: T): number {
return x.length;
}
len("hello"); // OK — string có length
len([1, 2, 3]); // OK — array có length
len({ length: 10, x: 1 }); // OK — object có length
len(42); // ❌ number không có length
Combo cực mạnh: K extends keyof T. Đảm bảo key là real key của object —
không chấp nhận string ngẫu nhiên. Đây là kỹ thuật chủ lực để viết "object accessor" type-safe:
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: "An", active: true };
const id = getProp(user, "id"); // id: number
const name = getProp(user, "name"); // name: string
const act = getProp(user, "active"); // act: boolean
getProp(user, "email");
// ❌ Argument of type '"email"' is not assignable to parameter of type 'keyof typeof user'
Nếu viết function getProp<T>(obj: T, key: keyof T): T[keyof T], return type là
union của mọi value type — bạn không biết cụ thể nào. Tách K ra giúp TS "ghi nhớ"
đúng key user đã truyền, từ đó suy ra T[K] chính xác.
4. Default type parameter
Giống default value cho function param, type parameter có thể có default. Cú pháp T = SomeType.
Khi gọi không spec, TS lấy default:
function createArray<T = string>(): T[] {
return [];
}
const a = createArray(); // a: string[] (default)
const b = createArray<number>(); // b: number[]
// Combo: default + constraint
interface ApiResponse<T extends object = {}> {
status: number;
data: T;
}
const r1: ApiResponse = { status: 200, data: {} };
const r2: ApiResponse<{ id: number }> = { status: 200, data: { id: 1 } };
Default rất hay khi viết library — user không spec gì vẫn dùng được, nhưng vẫn override khi cần type cụ thể.
5. Generic class
Class cũng nhận type parameter. Type parameter sống ở cấp class — mọi property/method đều dùng được:
class Stack<T> {
private items: T[] = [];
push(x: T): void {
this.items.push(x);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
get size(): number {
return this.items.length;
}
}
const numbers = new Stack<number>();
numbers.push(1);
numbers.push(2);
const top = numbers.pop(); // top: number | undefined
const strs = new Stack<string>();
strs.push("hello");
strs.push(42); // ❌ number không gán vào string
Mỗi instance Stack có một T cụ thể: Stack<number> và Stack<string>
là 2 type khác nhau. Khi compile, generic biến mất, chỉ còn class Stack JS thường — type là
công cụ tĩnh.
6. Generic interface và type alias
Interface và type alias cũng nhận type parameter. Đây là cách định nghĩa container type tái sử dụng được:
// Cách 1 — interface
interface Box<T> {
value: T;
}
// Cách 2 — type alias (tương đương)
type Box2<T> = { value: T };
const b1: Box<number> = { value: 42 };
const b2: Box<string> = { value: "hi" };
// Generic interface trong API — pattern phổ biến
interface Result<T, E = Error> {
ok: boolean;
data?: T;
error?: E;
}
const success: Result<number> = { ok: true, data: 42 };
const fail: Result<number, string> = { ok: false, error: "timeout" };
Function signature cũng có thể là generic interface — cách định nghĩa "callable generic":
// Interface mô tả function generic
interface Mapper {
<T, U>(arr: T[], fn: (x: T) => U): U[];
}
const myMap: Mapper = (arr, fn) => arr.map(fn);
myMap([1, 2], n => n * 2); // number[]
7. Utility types: Partial, Required, Readonly
Từ chương này về sau, ta dùng utility types — sẵn có trong TS. Đây là generic được viết sẵn để biến đổi type. Bộ 3 đầu tiên: bật/tắt optional và readonly.
| Utility | Tác dụng | Tương đương |
|---|---|---|
Partial<T> | Mọi property thành optional | { [K in keyof T]?: T[K] } |
Required<T> | Mọi property thành bắt buộc | { [K in keyof T]-?: T[K] } |
Readonly<T> | Mọi property thành readonly | { readonly [K in keyof T]: T[K] } |
interface User {
id: number;
name: string;
email: string;
active: boolean;
}
// updateUser nhận subset of User — chỉ field nào muốn đổi
function updateUser(id: number, patch: Partial<User>): User {
// ... merge với user hiện tại
return { id, name: "x", email: "x", active: true, ...patch };
}
updateUser(1, { name: "An" }); // OK
updateUser(1, { email: "a@b", active: false }); // OK
updateUser(1, {}); // OK (rỗng cũng được)
Partial chỉ "shallow" — không đi sâu
Partial<T> chỉ làm optional ở tầng 1. Nếu property là object lồng,
object đó vẫn nguyên cấu trúc, không tự optional bên trong.
interface Config {
ui: { theme: string; lang: string };
api: { url: string };
}
type P = Partial<Config>;
// = { ui?: { theme: string; lang: string }; api?: { url: string } }
const p: P = { ui: { theme: "dark" } }; // ❌ thiếu lang
Cần "đệ quy" → tự viết DeepPartial<T> bằng conditional/mapped type — chương 11 sẽ làm.
interface PartialConfig {
theme?: string;
lang?: string;
debug?: boolean;
}
type FullConfig = Required<PartialConfig>;
// = { theme: string; lang: string; debug: boolean } ← bỏ tất cả ?
function applyConfig(c: FullConfig) {
// c.theme đảm bảo có, không phải undefined
}
Readonly<T> hữu ích khi muốn nhấn mạnh "không được mutate" — vd state immutable, config app.
interface Point { x: number; y: number; }
const p: Readonly<Point> = { x: 1, y: 2 };
p.x = 5; // ❌ Cannot assign to 'x' because it is a read-only property
8. Utility types: Pick và Omit
Hai utility "ngược nhau" để tạo subtype: chọn hay loại một số key.
| Utility | Tác dụng | Ví dụ |
|---|---|---|
Pick<T, K> | Lấy chỉ key trong K | Pick<User, "id" | "name"> |
Omit<T, K> | Bỏ key trong K | Omit<User, "password"> |
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Trả API: không lộ password
type UserResponse = Omit<User, "password">;
// = { id: number; name: string; email: string; createdAt: Date }
// Item trong list — chỉ cần id + name
type UserListItem = Pick<User, "id" | "name">;
// = { id: number; name: string }
// Form đăng nhập — chỉ email + password
type LoginForm = Pick<User, "email" | "password">;
Chúng "lưỡng tính" — biểu diễn qua nhau:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// "Omit K" = "Pick mọi key của T trừ K"
Đây thực sự là cách Omit được định nghĩa trong lib.es5.d.ts của TS.
9. Utility type: Record<K, V>
Tạo object type với key trong union K, value type V. Cú pháp gọn hơn index signature.
// Object có chính xác 3 key Role → number (count user)
type Role = "admin" | "user" | "guest";
type RoleCount = Record<Role, number>;
// = { admin: number; user: number; guest: number }
const counts: RoleCount = {
admin: 2,
user: 100,
guest: 42,
};
counts.user; // number
counts.other; // ❌ Property 'other' does not exist
// String key bất kỳ — tương đương index signature
type Dict<V> = Record<string, V>;
// = { [key: string]: V }
const ages: Dict<number> = { an: 22, binh: 23 };
Record<K, V> vs index signature
Record<string, number> và { [key: string]: number } tương
đương. Record chỉ là "sugar" cho mapped type:
type Record<K extends keyof any, V> = {
[P in K]: V;
};
Khác biệt thực dụng: Record đẹp hơn khi K là union cụ thể (vd "a"|"b"|"c")
— nó "ghi" đủ key cho IntelliSense. Index signature đẹp hơn khi key thực sự "bất kỳ".
10. Utility types lọc union: Exclude, Extract, NonNullable
Bộ ba này làm việc với union. Hữu ích cho narrowing type, lọc literal, bỏ null/undefined.
| Utility | Ý nghĩa | Ví dụ |
|---|---|---|
Exclude<T, U> | Loại member của T nằm trong U | Exclude<"a"|"b"|"c", "a"> = "b"|"c" |
Extract<T, U> | Giữ lại member của T nằm trong U | Extract<"a"|"b"|1, string> = "a"|"b" |
NonNullable<T> | Loại null & undefined | NonNullable<string|null> = string |
type Status = "pending" | "success" | "error" | "cancelled";
// Bỏ "cancelled"
type ActiveStatus = Exclude<Status, "cancelled">;
// = "pending" | "success" | "error"
// Chỉ giữ "success" và "error"
type DoneStatus = Extract<Status, "success" | "error">;
// = "success" | "error"
// Bỏ null/undefined
type MaybeName = string | null | undefined;
type Name = NonNullable<MaybeName>; // = string
// Áp dụng trong function — narrow sau check
function trim(s: string | null): string {
if (s === null) return "";
const v: NonNullable<typeof s> = s; // v: string
return v.trim();
}
11. Utility types về function: ReturnType, Parameters, Awaited
Bộ 3 này "rút" thông tin từ type của function/promise. Cực kỳ hữu ích khi bạn không muốn viết tay lại type mà function khác đã định nghĩa.
| Utility | Input | Output |
|---|---|---|
ReturnType<F> | function type | Type của giá trị return |
Parameters<F> | function type | Tuple type của param |
Awaited<P> | Promise type | Type bên trong Promise (đệ quy) |
function createUser(name: string, age: number) {
return { id: Date.now(), name, age, active: true };
}
// Lấy return type — không cần định nghĩa lại
type User = ReturnType<typeof createUser>;
// = { id: number; name: string; age: number; active: boolean }
// Lấy tuple param
type Args = Parameters<typeof createUser>;
// = [name: string, age: number]
// Lấy param đầu
type FirstArg = Parameters<typeof createUser>[0]; // = string
typeof trước tên function?
createUser là value (function thật). ReturnType cần một type.
Toán tử typeof ở vị trí type "lấy type của value đó". Tương tự pattern hay gặp:
const config = { theme: "dark", lang: "vi" };
type Config = typeof config; // = { theme: string; lang: string }
Đây là typeof ở "tầng type" — không phải typeof runtime trả string. Cùng từ
khoá, 2 nghĩa khác nhau tuỳ ngữ cảnh.
async function fetchUser(): Promise<{ id: number; name: string }> {
return { id: 1, name: "An" };
}
// ReturnType — chỉ lấy Promise<...>, chưa unwrap
type R = ReturnType<typeof fetchUser>;
// R = Promise<{ id: number; name: string }>
// Awaited — lấy giá trị bên trong Promise
type User = Awaited<R>;
// User = { id: number; name: string }
// Combo phổ biến
type User2 = Awaited<ReturnType<typeof fetchUser>>;
// Awaited đệ quy — unwrap Promise lồng
type A = Awaited<Promise<Promise<number>>>; // = number
12. Tự viết utility — bước đầu
Bạn không phải lúc nào cũng phải dùng utility built-in — tự viết được khi cần. Ở chương 11 (Advanced Types) ta học đầy đủ conditional và mapped type. Chương này chỉ "nếm thử":
// 1. Nullable — thêm null vào union
type Nullable<T> = T | null;
const name: Nullable<string> = null; // OK
const name2: Nullable<string> = "hello"; // OK
// 2. Optional — thêm null + undefined
type Maybe<T> = T | null | undefined;
// 3. ValueOf — tương tự keyof nhưng cho value type
type ValueOf<T> = T[keyof T];
interface Color { red: 1; green: 2; blue: 3; }
type ColorValue = ValueOf<Color>; // = 1 | 2 | 3
// 4. DeepPartial — phiên bản đệ quy của Partial
// (đầy đủ ở chương 11 — đây là intro)
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object
? DeepPartial<T[K]>
: T[K];
};
interface Config {
ui: { theme: string; lang: string };
api: { url: string; timeout: number };
}
const p: DeepPartial<Config> = {
ui: { theme: "dark" }, // thiếu lang — OK vì đã đệ quy
};
Quy ước phổ biến: tạo file src/types/utils.ts (hoặc shared/types.ts),
export type những utility tự viết. Một số project lớn dùng package
type-fest (Sindre
Sorhus) — bộ ~150 utility chất lượng cao, đỡ tự viết lại.
13. Bảng tổng kết utility types
Một bảng để tra cứu nhanh sau khi đã đọc xong từng phần:
| Utility | Input → Output | Dùng khi |
|---|---|---|
Partial<T> | Mọi property → optional | Update DTO, patch object |
Required<T> | Mọi property → bắt buộc | Sau khi merge default, đảm bảo đủ field |
Readonly<T> | Mọi property → readonly | State immutable, config |
Pick<T, K> | Lấy key trong K | List item, form subset |
Omit<T, K> | Bỏ key trong K | Hide password, ẩn id khi tạo mới |
Record<K, V> | Object {K → V} | Dictionary key cố định |
Exclude<T, U> | Bỏ member union T trong U | Narrow status, lọc literal |
Extract<T, U> | Giữ member T trong U | Lọc loại event, giữ string trong mixed |
NonNullable<T> | Bỏ null + undefined | Sau narrowing, force non-null |
ReturnType<F> | Lấy return của F | Reuse type của function khác |
Parameters<F> | Lấy tuple param của F | Wrap/decorate function |
Awaited<P> | Unwrap Promise (đệ quy) | Lấy giá trị async return |
Bài tập
Bài 1 — Generic map function
Viết function map<T, U>(arr: T[], fn: (x: T) => U): U[] — không dùng
Array.prototype.map. Test với:
map([1, 2, 3], n => n.toString())→["1", "2", "3"]kiểustring[]map(["a", "bb", "ccc"], s => s.length)→[1, 2, 3]kiểunumber[]
Đáp án
function map<T, U>(arr: T[], fn: (x: T) => U): U[] {
const out: U[] = [];
for (const x of arr) {
out.push(fn(x));
}
return out;
}
const strs = map([1, 2, 3], n => n.toString());
// strs: string[] ← TS infer T=number, U=string
const lens = map(["a", "bb", "ccc"], s => s.length);
// lens: number[]
Điểm chính: 2 type parameter — input element type và output element type — TS tự suy ra cả 2 từ argument.
Bài 2 — groupBy
Viết function groupBy<T, K extends string | number>(arr: T[], keyFn: (x: T) => K): Record<K, T[]>
— gom mảng thành object theo key. Ví dụ:
const users = [
{ name: "An", role: "admin" },
{ name: "Binh", role: "user" },
{ name: "Cuong", role: "admin" },
];
groupBy(users, u => u.role);
// = { admin: [An, Cuong], user: [Binh] }
Đáp án
function groupBy<T, K extends string | number>(
arr: T[],
keyFn: (x: T) => K
): Record<K, T[]> {
const out = {} as Record<K, T[]>;
for (const x of arr) {
const k = keyFn(x);
(out[k] ??= []).push(x);
}
return out;
}
Constraint K extends string | number đảm bảo có thể dùng làm key object. Pattern
(out[k] ??= []).push(x) dùng logical-nullish-assignment (ES2021): nếu key chưa
có → tạo array rỗng → push.
Bài 3 — Type-safe event emitter
Cho:
type EventMap = {
click: MouseEvent;
submit: SubmitEvent;
keypress: KeyboardEvent;
};
Viết function on<K extends keyof EventMap>(event: K, handler: (e: EventMap[K]) => void): void.
Yêu cầu:
on("click", e => e.clientX)hợp lệ (e là MouseEvent)on("submit", e => e.preventDefault())hợp lệon("click", e => e.key)❌ (key chỉ có ở KeyboardEvent)on("unknown", ...)❌ (không có trong EventMap)
Đáp án
type EventMap = {
click: MouseEvent;
submit: SubmitEvent;
keypress: KeyboardEvent;
};
function on<K extends keyof EventMap>(
event: K,
handler: (e: EventMap[K]) => void
): void {
document.addEventListener(event, handler as EventListener);
}
on("click", e => console.log(e.clientX)); // OK
on("submit", e => e.preventDefault()); // OK
on("click", e => e.key); // ❌ key không có trên MouseEvent
on("unknown", () => {}); // ❌ "unknown" không phải keyof EventMap
Đây là pattern core của type-safe event system — react synthetic events, Node EventEmitter typed, RxJS. Cả 2 lỗi compile-time, không cần runtime check.
Bài 4 — User DTOs với Pick/Omit
Cho interface User 10 field:
interface User {
id: number;
email: string;
password: string;
name: string;
avatar: string;
bio: string;
role: "admin" | "user";
active: boolean;
createdAt: Date;
updatedAt: Date;
}
Hãy tạo 4 type sau bằng Pick/Omit/Partial:
CreateUserDto— bỏ id, createdAt, updatedAt (server tự sinh)UpdateUserDto— như CreateUserDto nhưng mọi field optionalUserResponse— bỏ passwordUserListItem— chỉ id, name, avatar
Đáp án
// 1. Tạo mới — bỏ field server-generated
type CreateUserDto = Omit<User, "id" | "createdAt" | "updatedAt">;
// 2. Update — như tạo mới + mọi field optional
type UpdateUserDto = Partial<CreateUserDto>;
// Hoặc: Partial<Omit<User, "id" | "createdAt" | "updatedAt">>
// 3. Response — không lộ password
type UserResponse = Omit<User, "password">;
// 4. List — chỉ thông tin để hiển thị
type UserListItem = Pick<User, "id" | "name" | "avatar">;
// Test
const dto: CreateUserDto = {
email: "a@b.com",
password: "x",
name: "An",
avatar: "",
bio: "",
role: "user",
active: true,
};
const patch: UpdateUserDto = { name: "An mới" }; // OK chỉ 1 field
Pattern này là chuẩn cho REST API. Một interface User duy nhất sinh ra 4 type khác nhau cho 4 ngữ cảnh — không lặp code, đổi User → tất cả tự đồng bộ.
Bài 5 — OptionalKeys<T>
Viết utility type OptionalKeys<T> trả về union của các key trong T mà property
là optional (có ?). Ví dụ:
interface User {
id: number;
name: string;
email?: string;
bio?: string;
}
type Opt = OptionalKeys<User>; // = "email" | "bio"
Gợi ý: dùng conditional type. So sánh {} với Pick<T, K> — nếu một
key K là optional, {} có thể assignable vào Pick<T, K>.
Đáp án
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
// Đối xứng — key bắt buộc
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
// Test
interface User { id: number; name: string; email?: string; bio?: string; }
type Opt = OptionalKeys<User>; // "email" | "bio"
type Req = RequiredKeys<User>; // "id" | "name"
Cơ chế: với mỗi K, kiểm tra {} extends Pick<T, K>:
- Nếu K optional →
Pick<T, K>là{ K?: ... }→{}assignable → kết quả K. - Nếu K bắt buộc →
Picklà{ K: ... }→{}không assignable → kết quả never.
Cuối cùng [keyof T] "indexed access" lấy union các value — never tự loại bỏ.
Đây là pattern conditional + mapped type "kinh điển" — chương 11 sẽ phân tích kỹ.
Quiz
Với function f<T>(x: T): T { return x; }, khi gọi f(5), T là gì?
Xem đáp án
T = number. TS dùng type inference — nhìn vào argument 5 có type
number, đối chiếu với param x: T → suy ra T = number. Không cần viết
f<number>(5) tường minh (vẫn đúng nhưng dư).
Partial<T> có đệ quy không (làm optional ở nested object)?
Xem đáp án
Không. Partial chỉ làm optional ở tầng 1. Nested object
vẫn nguyên cấu trúc. Muốn đệ quy phải tự viết DeepPartial bằng conditional type
(xem mục 12 và chương 11).
Pick<T, K> và Omit<T, K> "đối xứng" thế nào?
Xem đáp án
Omit<T, K> = Pick<T, Exclude<keyof T, K>>. Tức là "Omit K" tương đương
"Pick tất cả key của T trừ K". Đây chính là cách Omit được định nghĩa trong
lib.es5.d.ts của TypeScript.
Khác nhau giữa Record<string, number> và { [key: string]: number }?
Xem đáp án
Tương đương về mặt type. Record là "sugar" cho mapped type:
Record<K, V> = { [P in K]: V }. Khác biệt thực dụng: dùng Record khi
K là union cụ thể (vd "a"|"b"|"c") — IntelliSense gợi ý đủ key. Index signature
thuận tự nhiên hơn khi key thực sự "bất kỳ string".
Vì sao viết ReturnType<typeof foo> mà không phải ReturnType<foo>?
Xem đáp án
Vì foo là value (function thật khi runtime), còn ReturnType
nhận type. Toán tử typeof ở tầng type "chuyển" value thành type
tương ứng — không phải typeof runtime trả string. Cùng từ khoá, 2 nghĩa khác nhau tuỳ
vị trí (type position vs value position).
Generic class có default cho type parameter không?
Xem đáp án
Có. Cú pháp giống function: class Container<T extends object = {}>.
Khi tạo instance không spec type, TS lấy default. Pattern này phổ biến trong React (vd
useState<T = undefined>), state library.
Required<T> có loại bỏ ? kể cả những field vốn đã optional?
Xem đáp án
Có. Required<T> ép tất cả property thành bắt buộc, kể cả original
?. Định nghĩa: { [K in keyof T]-?: T[K] } — dấu -? là
"remove optional modifier". Tương tự Readonly dùng readonly và có
"mặt nghịch" là -readonly.
Tổng kết
Sau chương 10, bạn nên đã thành thạo:
- Generic function: 1, 2, nhiều type parameter; hiểu inference vs spec tường minh.
- Constraint:
T extends ..., đặc biệt comboK extends keyof T. - Default type parameter:
T = string, gọn API mà vẫn linh hoạt. - Generic class và generic interface/type: container, callable signature.
- 11 utility types built-in: bốn nhóm — modifier (Partial/Required/Readonly), select (Pick/Omit/Record), filter union (Exclude/Extract/NonNullable), từ function (ReturnType/Parameters/Awaited).
- Cảm giác đầu tiên với conditional & mapped type tự viết — sẽ "deep dive" ở chương 11.
Kết nối
- Chương 11 (TS Advanced Types) — conditional type, mapped type, template literal type, infer. Đây là engine đằng sau mọi utility hôm nay; sau chương 11 bạn sẽ viết được mọi utility tự thân.
- Chương 12 (TS Practical) — áp dụng generic + utility vào project React + Node thật. tsconfig khắt khe, declaration file, integration với Express/Fastify.
- Dart Chương 8 (Generic) — đối chiếu generic Dart (cũng có
extends) vs TS (cộng thêm conditional). Dart không có utility "biến đổi type" tương đương Pick/Omit — type system của TS mạnh hơn ở mặt này. - Python typing (pillar tương lai) —
TypeVar,Generic[T],TypedDict— tương ứng generic/interface, nhưng yếu hơn về utility.