TypeScript
Advanced types — Generic, Conditional, Mapped, Utility
Generic + constraint, conditional types, mapped types, infer, template literal, decorator, utility types.
1. Generic — cốt lõi
function identity<T>(x: T): T {
return x;
}
identity<number>(42);
identity("hello"); // type inferred
// Generic function với nhiều param
function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}
// Generic class
class Box<T> {
constructor(public value: T) {}
map<U>(fn: (x: T) => U): Box<U> {
return new Box(fn(this.value));
}
}
// Generic interface
interface Repo<T> {
findById(id: number): T | undefined;
add(item: T): void;
}
2. Generic constraint — extends
// T phải có property "length"
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest("foo", "bars"); // OK — string có length
longest([1, 2], [1, 2, 3]); // OK — array có length
longest(1, 2); // ❌ — number không có length
// keyof constraint — key của object
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: "Alice" };
get(user, "name"); // string
get(user, "age"); // ❌ — "age" không phải key của user
⭐ Câu PV cực hay:"
keyof T là gì?""
keyof Ttrả về union các key của T dưới dạng string literal type. Ví dụkeyof { id: number; name: string }="id" | "name". Dùng để type-safe access property qua tên key — phổ biến với pattern get/set theo path."
3. Generic default
interface ApiResponse<TData = unknown, TError = Error> {
data: TData;
error?: TError;
}
const r1: ApiResponse = { data: "x" }; // TData = unknown
const r2: ApiResponse<User> = { data: {...} };
4. Conditional types — T extends U ? X : Y
// Cơ bản
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<"hello">; // "yes"
type B = IsString<42>; // "no"
// Distributive — apply cho từng union member
type ToArray<T> = T extends any ? T[] : never;
type C = ToArray<string | number>; // string[] | number[] (không phải (string|number)[])
// Non-distributive — wrap trong tuple
type ToArrayBox<T> = [T] extends [any] ? T[] : never;
type D = ToArrayBox<string | number>; // (string | number)[]
Tại sao conditional với generic naked phân phối qua union? Vì TS coi
T extends X ? ... : ... với T "naked" là distributive — chạy cho từng member rồi gộp.5. infer — đặt biến type trong conditional
// Lấy return type của function
type ReturnTypeOf<F> = F extends (...args: any) => infer R ? R : never;
type T1 = ReturnTypeOf<() => string>; // string
type T2 = ReturnTypeOf<typeof Math.random>; // number
// Lấy parameter type
type ParamsOf<F> = F extends (...args: infer P) => any ? P : never;
type T3 = ParamsOf<(a: number, b: string) => void>; // [number, string]
// Unbox Promise
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type T4 = UnwrapPromise<Promise<string>>; // string
type T5 = UnwrapPromise<number>; // number
// Unbox Array
type Element<T> = T extends (infer E)[] ? E : never;
type T6 = Element<number[]>; // number
type T7 = Element<[string, number]>; // string | number
⭐
infer cực hay trong PV junior/senior. Khi hiểu được pattern này → bạn đọc được mọi utility type built-in.6. Mapped types — biến đổi shape
// Cơ bản — map qua key
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Partial<T> = { [K in keyof T]?: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] }; // remove ?
type Mutable<T> = { -readonly [K in keyof T]: T[K] }; // remove readonly
// Key remapping (TS 4.1+) — `as`
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
// Getters<{ name: string; age: number }>
// = { getName: () => string; getAge: () => number }
7. Template literal types
type Greeting = `Hello, ${string}!`;
const g1: Greeting = "Hello, world!"; // OK
const g2: Greeting = "Bye"; // ❌
// Combine union
type Color = "red" | "blue";
type Size = "small" | "large";
type Variant = `${Color}-${Size}`;
// "red-small" | "red-large" | "blue-small" | "blue-large"
// Tách string
type SplitFirst<S extends string> =
S extends `${infer Head}/${infer Tail}` ? Head : never;
type T = SplitFirst<"users/123/profile">; // "users"
Template literal type + infer cho phép parse string lúc compile — type-safe API path, SQL builder, etc. Power user feature.
⭐ TS 7.0 fix Unicode: Template literal type với
infer giờ xử lý đúng emoji / surrogate pairs:type HeadTail<S> = S extends `${infer Head}${infer Tail}` ? [Head, Tail] : never;
// TS 7.0: HeadTail<"👋🌍"> => ["👋", "🌍"] — đúng!
// TS cũ: HeadTail<"👋🌍"> => ["\uD83D", "\uDC4B🌍"] — sai do surrogate pairs
8. Utility types built-in (phải thuộc)
| Utility | Tác dụng |
|---|---|
Partial<T> | Mọi field thành optional |
Required<T> | Mọi field thành bắt buộc |
Readonly<T> | Mọi field thành readonly |
Pick<T, K> | Chọn subset key K |
Omit<T, K> | Bỏ subset key K |
Record<K, V> | Object với key K, value V |
Exclude<T, U> | T loại U (cho union) |
Extract<T, U> | T giao U |
NonNullable<T> | T loại null/undefined |
ReturnType<F> | Return type của function F |
Parameters<F> | Tuple parameter của F |
Awaited<T> | Unbox Promise lồng nhau |
InstanceType<C> | Instance của constructor class |
Uppercase<S> / Lowercase<S> / Capitalize<S> / Uncapitalize<S> | Biến đổi string literal |
interface User { id: number; name: string; age: number; }
type UserPreview = Pick<User, "id" | "name">; // { id; name }
type UserNoAge = Omit<User, "age">; // { id; name }
type UserPartial = Partial<User>; // tất cả optional
type UserName = User["name"]; // string (lookup type)
type UserKeys = keyof User; // "id" | "name" | "age"
type Status = "active" | "inactive" | "deleted";
type Live = Exclude<Status, "deleted">; // "active" | "inactive"
type Dead = Extract<Status, "deleted">; // "deleted"
async function fetch(): Promise<User> { ... }
type R = Awaited<ReturnType<typeof fetch>>; // User
⭐ Câu PV nhiều khả năng:"
Pick, Omit, Partial định nghĩa thế nào?" — Hỏi để check bạn có hiểu mapped + conditional types không.type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type Partial<T> = { [P in keyof T]?: T[P] };
9. Index access type — T[K]
interface User { id: number; name: string; address: { city: string } }
type Name = User["name"]; // string
type Id = User["id"]; // number
type City = User["address"]["city"]; // string nested
// Combine với keyof — lookup mọi value type
type UserValue = User[keyof User]; // string | number | { city: string }
// Array element
type Items = string[];
type Item = Items[number]; // string
// Tuple element
type Tup = [string, number, boolean];
type T0 = Tup[0]; // string
type Any = Tup[number]; // string | number | boolean
10. Decorator (TC39 stage 3, stable TS 5.0+, metadata stable 5.9)
// Class decorator
function logged<T extends { new (...args: any[]): {} }>(target: T) {
return class extends target {
constructor(...args: any[]) {
super(...args);
console.log(`Created ${target.name}`);
}
};
}
@logged
class Service { }
// Method decorator (TC39 stage 3 syntax)
function logTime(originalMethod: any, context: ClassMethodDecoratorContext) {
return function (this: any, ...args: any[]) {
console.time(String(context.name));
const result = originalMethod.apply(this, args);
console.timeEnd(String(context.name));
return result;
};
}
class Calculator {
@logTime
sum(a: number, b: number) { return a + b; }
}
Có 2 dạng decorator:
- Legacy / experimental (
experimentalDecorators: true+emitDecoratorMetadata) — dùng cho NestJS/Angular/TypeORM truyền thống. - TC39 stage 3 — chuẩn ECMAScript, stable từ TS 5.0. Khác signature.
11. Module augmentation & declaration merging
// Mở rộng module ngoài
declare module "express" {
interface Request {
user?: { id: number; email: string };
}
}
// Sau đó dùng req.user trong route handler — TS biết kiểu
app.get("/me", (req, res) => {
res.json(req.user); // typed
});
12. Common patterns — phải biết
Branded type / nominal type
type UserId = number & { __brand: "UserId" };
type OrderId = number & { __brand: "OrderId" };
function getUser(id: UserId) { ... }
const uid = 1 as UserId;
const oid = 2 as OrderId;
getUser(uid); // OK
getUser(oid); // ❌ — chống nhầm ID
Type-safe object key iteration
// Object.keys trả string[] — TS không biết key cụ thể
function typedKeys<T extends object>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}
for (const key of typedKeys(user)) {
user[key]; // typed correctly
}
Builder pattern type-safe
class QueryBuilder<T = {}> {
constructor(private state: T = {} as T) {}
where<K extends string, V>(key: K, value: V): QueryBuilder<T & Record<K, V>> {
return new QueryBuilder({ ...this.state, [key]: value } as T & Record<K, V>);
}
build(): T { return this.state; }
}
const q = new QueryBuilder()
.where("name", "Alice")
.where("age", 25)
.build();
// q: { name: string; age: number }
13. TypeScript 7.0 — Go compiler & song song hóa type-checking
TS 7.0 là bản nâng cấp lớn nhất từ trước đến nay: toàn bộ compiler được viết lại bằng Go, thay thế JavaScript cũ. Điểm nổi bật nhất là type-checking song song — nhiều luồng kiểm tra type cùng lúc.
CLI flags mới
# Type-checking song song
tsgo --checkers 8 # 8 worker thread kiểm tra type (mặc định 4)
# Build song song cho monorepo
tsgo --builders 4 # 4 project reference builder song song
# Chạy đơn luồng (khi debug)
tsgo --singleThreaded
# Kết hợp: --checkers 4 --builders 4 = tối đa 16 type-checker đồng thời
Cách parallel type-checking hoạt động
- Mỗi worker nhận input file giống hệt nhau
- Phân chia công việc deterministic — cùng kết quả mỗi lần chạy
stableTypeOrdering: truebắt buộc — đảm bảo kết quả nhất quán giữa các worker- Parsing và emitting cũng được song song hóa tự động
- Dùng nhiều checker → dùng nhiều RAM hơn — cần tune cẩn thận
- Hiệu ứng cấp số nhân với
--builders × --checkers
Performance tuning
| Tình huống | Gợi ý |
|---|---|
| Dự án nhỏ (< 500 file) | --checkers 2 — đủ dùng, tiết kiệm RAM |
| Dự án vừa (500 – 5000 file) | --checkers 4 (mặc định) |
| Dự án lớn (> 5000 file) | --checkers 8 hoặc cao hơn, monitor RAM |
| Monorepo nhiều package | --builders 4 cho project references |
| CI/CD pipeline | --checkers 8 --builders 2 — ưu tiên type-check từng project |
| Local dev | --incremental + --watch — rebuild nhanh |
Chiến lược monorepo
# Build toàn bộ monorepo với project references
tsgo --build tsconfig.json --builders 4
# Chỉ type-check dự án hiện tại (không build dependency)
tsgo --noEmit --checkers 8
# Watch mode — rebuild khi file thay đổi (dùng @parcel/watcher port)
tsgo --watch --incremental
stableTypeOrdering — union ordering bắt buộc
⭐ Quan trọng cho advanced types: Trong TS 7.0,
stableTypeOrderingluôn bật, không tắt được.- Union types có thứ tự deterministic giữa mọi lần build
- Conditional types phụ thuộc thứ tự union sẽ chạy nhất quán
- Có thể lộ bug nếu code của bạn vô tình dựa vào thứ tự union cũ (không ổn định)
// TS 7.0: union ordering luôn deterministic
type A = "a" | "b" | "c"; // luôn "a" | "b" | "c" (theo thứ tự khai báo)
type B = "c" | "b" | "a"; // luôn "a" | "b" | "c" (TS sắp xếp canonical)
// Conditional distributive qua union — kết quả nhất quán giữa mọi lần build
type Filter<T, U> = T extends U ? T : never;
type R = Filter<"a" | "b" | "c", "b" | "c">; // "b" | "c" — deterministic
Programmatic API — CẢNH BÁO
🚨 TS 7.0 không có Compiler API cũ!
ts.createProgram,ts.createSourceFile,ts.transform— đã bị xóa- Các tool như ts-morph, ts-jest, ts-node hiện không hoạt động với tsgo
- Compiler API mới sẽ có trong TS 7.1
- Giải pháp tạm thời: giữ TS 6 cho tool cần API, dùng tsgo cho build và type-check
# Build & type-check — dùng tsgo
npx tsgo --noEmit
# Tool cần Compiler API — dùng TS 6
npx tsc --version # vẫn là TS 6.x
Build mode cải tiến
# Watch mode (viết lại bằng Go, dùng @parcel/watcher port)
tsgo --watch
# Build kèm declaration files
tsgo --declaration
# Build incremental
tsgo --incremental
# Project references (hỗ trợ đầy đủ)
tsgo --build tsconfig.json
14. TS 6.0 — tính năng chuyển tiếp
noUncheckedSideEffectImports
// tsconfig.json — mặc định bật từ TS 6.0+
{
"compilerOptions": {
"noUncheckedSideEffectImports": true
}
}
Bắt lỗi import side-effect không kiểm soát:
// ❌ Bị bắt nếu file import không có type/value export
import "./polyfills"; // side-effect import — phải khai báo rõ
// ✅ OK nếu polyfills.ts có export ít nhất 1 type/value
import "./setup"; // setup.ts export hàm init()
Temporal API — kiểu dựng sẵn
// Temporal API (Stage 3) — có sẵn lib typing trong TS 6.0+
const now = Temporal.Now.plainDateISO(); // Temporal.PlainDate
const zoned = Temporal.Now.zonedDateTimeISO("Asia/Saigon"); // Temporal.ZonedDateTime
const dur = Temporal.Duration.from({ hours: 2, minutes: 30 }); // Temporal.Duration
RegExp.escape()
// Escape string để dùng trong RegExp — có typing sẵn
const escaped = RegExp.escape(".$?*+{}|()[]"); // "\\.\\$\\?\\*\\+\\{\\}\\|\\(\\)\\[\\]"
const re = new RegExp(escaped); // RegExp
Iterable & AsyncIterable trên DOM types
// DOM collections giờ implement Iterable / AsyncIterable
const nodes = document.querySelectorAll("div");
for (const el of nodes) { } // ✅ Iterable<HTMLDivElement>
const stream = response.body; // ReadableStream
for await (const chunk of stream) { } // ✅ AsyncIterable<Uint8Array>
⭐ Câu PV:"TS 7.0 thay đổi gì lớn nhất?"
"Compiler viết lại bằng Go, hỗ trợ type-checking song song với
--checkers.stableTypeOrderingbắt buộc khiến union ordering deterministic. Compiler API cũ bị xóa — tool như ts-morph/ts-node chưa hoạt động cho đến TS 7.1. Build mode có--watch,--incremental,--declarationchạy quatsgo."
Tiếp theo: Câu hỏi phỏng vấn TypeScript