Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026
TypeScript
TypeScript là superset của JavaScript có static typing, compile sang JS.
Lợi ích: phát hiện lỗi tại compile time, IDE support tốt hơn (autocomplete, refactoring), code tự documentation hóa. Không thay thế JS mà enhance nó. Hữu ích nhất ở dự án lớn, team, và codebases cần maintain lâu dài.
Type annotation là khai báo kiểu tường minh cho biến, parameter, return value: let name: string = 'John'.
- TypeScript cũng có type inference tự suy luận kiểu từ giá trị gán.
- Annotation thường chỉ cần cho function parameters, return types, và khi inference không đủ thông tin.
function greet(name: string): string {
return 'Hi ' + name;
}
let count: number = 0; // annotation thường dư (inference đủ)
let data: unknown; // cần annotation vì chưa gánany: tắt type checking, không an toàn, tránh dùng. unknown: type-safe any, phải kiểm tra type trước khi dùng, không thể assign cho typed variable mà không narrow. never: không bao giờ có giá trị, dùng cho hàm luôn throw, hoặc exhaustive checks. unknown >> any về safety.
Union type A | B: value có thể là A hoặc B — ví dụ string | number cho phép cả hai.
- Intersection type
A & B: value phải có tất cả properties của A lẫn B — ví dụtype AdminUser = User & { adminRole: string }tạo type có đủ fields của User cộng thêm adminRole. - Union dùng khi một giá trị có thể là nhiều loại (parameter linh hoạt), intersection dùng khi cần combine nhiều interface/type lại.
- Bẫy: intersection của 2 primitive types không tương thích (như
string & number) cho ra typenever.
Type assertion overrides TS inference at compile time without any runtime check. value as Type nói với TS "tin tôi, tôi biết kiểu này".
- Dùng khi TS không thể infer (DOM queries, JSON.parse).
- Tránh dùng để cast bừa bãi — sẽ mất type safety.
as unknown as Type(double assertion) là dấu hiệu code có vấn đề.
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const data = JSON.parse(json) as UserConfig; // vẫn cần validate runtimeTuple là array với số lượng và kiểu phần tử cố định.
- Phần tử có thể có tên.
- Hỗ trợ optional (?) và rest elements.
- Thường dùng cho function return nhiều giá trị, CSV rows, React useState.
type Point = [number, number];
type Entry = [key: string, value: number]; // named elements
const [count, setCount]: [number, React.Dispatch<React.SetStateAction<number>>]
= useState(0);
// Hoặc đơn giản hơn:
const pair: [string, number] = ['age', 25];strict: true bật nhiều checks: strictNullChecks (null/undefined không assign cho typed vars), strictFunctionTypes, strictPropertyInitialization, noImplicitAny, noImplicitThis, strictBindCallApply. Luôn bật strict: true cho dự án mới. Khi migrate JS sang TS có thể bật từng flag dần.
Lưu ý: TS 4.4 thêm useUnknownInCatchVariables vào strict — catch variable từ any chuyển thành unknown, bắt buộc narrow trước khi dùng. Điều này thường làm bối rối khi migrate dự án cũ.
- Interface: có thể extend và merge (declaration merging), chỉ describe object shapes.
- Type alias: linh hoạt hơn (union, intersection, primitives, tuples, conditional types), không merge.
- Quy tắc: dùng interface cho public API và khi cần extend, dùng type cho unions/intersections và khi cần advanced type features.
Interface: interface Child extends Parent { ... } và extends nhiều: extends A, B.
- Type: type Combined = A & B (intersection).
- Interface có thể extend type và ngược lại.
interface Animal { name: string }
interface Pet extends Animal { owner: string }
interface Named { label: string }
interface RegisteredPet extends Pet, Named { id: number } // đa extends
// Type intersection tương đương:
type Combined = Pet & Named & { id: number };extends kiểm tra compatibility (không thể re-declare incompatible property), còn & intersection merge tất cả.
Optional property (prop?: Type) có thể có hoặc không, type là Type | undefined khi access.
Khác với prop: Type | undefined — cái sau vẫn yêu cầu key có mặt khi tạo object. readonly property không thể reassign sau khởi tạo (chỉ compile time). Readonly<T> làm tất cả properties readonly.
interface User {
id: number;
nickname?: string; // caller có thể bỏ qua
bio: string | undefined; // caller phải cung cấp key, nhưng value có thể undefined
readonly createdAt: Date; // không thể reassign
}Partial<T> làm tất cả properties của T thành optional. Required<T> làm tất cả optional thành required.
Cả hai chỉ shallow.
interface User { id: number; name: string; bio?: string }
// Partial: dùng cho PATCH requests
function updateUser(id: string, data: Partial<User>) { /* ... */ }
updateUser('1', { name: 'New Name' }); // OK, không cần gửi toàn bộ
// Required: enforce tất cả fields sau validation
const validated: Required<User> = { id: 1, name: 'An', bio: 'Dev' };Record<Keys, Value> tạo object type với specific keys và value type.
- Khác index signature: Keys có thể là specific union (yêu cầu tất cả keys có mặt, không optional).
- Rõ ràng hơn và type-safe hơn index signature.
const routes: Record<'home' | 'about' | 'contact', string> = {
home: '/',
about: '/about',
contact: '/contact', // thiếu key này → Error
};
// Dùng với string key rộng hơn:
const cache: Record<string, unknown> = {};Pick<T, K> tạo type chỉ với subset of properties K từ T: Pick<User, 'id' | 'name'> chỉ giữ lại id và name. Omit<T, K> là ngược lại: tạo type với tất cả properties trừ K, ví dụ Omit<User, 'password'> để không lộ mật khẩu trong response.
- Hữu ích cho DTOs, API response shaping, và form state management khi chỉ cần một phần của type.
- Mẹo: dùng Omit khi cần loại ít field, Pick khi cần giữ ít field — chọn cái nào viết ngắn hơn.
Literal types là type chính xác là một giá trị cụ thể: type Direction = 'left' | 'right' | 'up' | 'down' — chỉ chấp nhận đúng 4 giá trị đó.
- Kết hợp với union tạo enum-like types mà không có runtime overhead của enum thật.
Ví dụ thực tế: type Status = 'loading' | 'success' | 'error' cho API state.
- Bẫy: TypeScript suy luận
let dir = 'left'làstring, không phải literal — dùngconsthoặcas constđể giữ literal type. - Là nền tảng của discriminated unions và type narrowing.
Type narrowing là thu hẹp type trong code branch dựa trên kiểm tra.
TypeScript tự động narrow trong if/else, switch, ternary.
function process(val: string | number | Date) {
if (typeof val === 'string') return val.toUpperCase(); // typeof
if (val instanceof Date) return val.toISOString(); // instanceof
return val.toFixed(2); // number
}
// User-defined type guard
function isUser(obj: unknown): obj is User {
return typeof obj === 'object' && obj !== null && 'id' in obj;
}! sau expression nói với TS rằng value không phải null/undefined.
- Hữu ích khi biết chắc value tồn tại nhưng TS không thể prove.
- Tránh lạm dụng vì có thể gây runtime error.
- Tốt hơn là dùng optional chaining hoặc explicit null check.
const el = document.querySelector('.btn')!; // assert not null
el.addEventListener('click', handler); // OK, no TS error
// Safer alternative:
const el2 = document.querySelector('.btn');
if (el2) el2.addEventListener('click', handler);- Enum khai báo tập hợp named constants.
- Numeric enum tự động assign 0,1,2... có reverse lookup nhưng gây footgun (Direction[0] = 'Up').
- String enum cần gán tường minh, dễ debug hơn, không có reverse lookup.
enum Direction { Up, Down } // numeric: Up=0, Down=1
Direction[0] // "Up" — reverse lookup
enum Status { Active = 'ACTIVE', Inactive = 'INACTIVE' } // string
// Lưu ý 2024+: TS community khuyến nghị dùng const object thay enum
// vì tree-shaking tốt hơn và không sinh runtime code thừa
const Direction = { Up: 'up', Down: 'down' } as const;
type Direction = typeof Direction[keyof typeof Direction];satisfies (TS 4.9) kiểm tra type mà không mở rộng type declaration. Khác as: satisfies vẫn infer type cụ thể nhất có thể và báo lỗi nếu không match.
Ví dụ: const config = {...} satisfies Config - TS check config đúng Config nhưng giữ literal types của values.
as const biến values thành readonly literal types cụ thể nhất. Array thành readonly tuple, object properties thành readonly literal types.
Ví dụ: const dirs = ['left', 'right'] as const tạo type readonly ['left', 'right'] thay string[]. Hữu ích để tạo strongly-typed constants.
Index signature cho phép object có keys không biết trước.
Tất cả explicit properties phải compatible với index signature type.
interface StringMap { [key: string]: string }
const headers: StringMap = { 'Content-Type': 'application/json' };
// Lưu ý: explicit properties cũng phải match
interface WithLength {
[key: string]: string;
length: number; // Error! length phải là string
}
// Record<K,V> thường rõ ràng hơn:
const routes: Record<string, string> = { home: '/', about: '/about' };Discriminated union là union trong đó mỗi member có common literal property (discriminant).
TypeScript narrow type tự động khi check discriminant.
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'triangle'; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.side ** 2;
case 'triangle':
return (shape.base * shape.height) / 2;
}
}Giúp type-safe handling cases khác nhau, TypeScript sẽ báo lỗi nếu thiếu case.
TypeScript hỗ trợ recursive type aliases.
Hữu ích cho tree structures, nested JSON, linked lists.
type TreeNode<T> = {
value: T;
children: TreeNode<T>[]; // tự tham chiếu
};
// JSON có thể dùng JSON type chuẩn:
type Json = string | number | boolean | null
| Json[] | { [key: string]: Json };Interface luôn hỗ trợ recursive (vì là named type), type alias cũng hỗ trợ từ TS 3.7.
TypeScript kiểm tra extra properties khi assign object literal trực tiếp vào typed variable.
Nhưng assign qua biến trung gian không bị check — đây là behavior khá bất ngờ.
interface Config { host: string; port: number }
// Object literal — excess property check:
const cfg: Config = { host: 'localhost', port: 3000, debug: true }; // Error!
// Qua biến trung gian — không bị check:
const raw = { host: 'localhost', port: 3000, debug: true };
const cfg2: Config = raw; // OK (chỉ check structural compatibility)Behavior này giúp phát hiện typos trong config objects.
Generics cho phép tạo components (functions, classes, interfaces) hoạt động với nhiều kiểu dữ liệu mà vẫn type-safe.
Thay dùng any, dùng type parameter <T>.
// Không generic — mất type info
function identity(arg: any): any { return arg; }
// Generic — type-safe
function identity<T>(arg: T): T { return arg; }
const n = identity(42); // n: number
const s = identity('hi'); // s: string
// Generic interface
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
const res: ApiResponse<User[]> = await fetchUsers();Tái sử dụng code mà không mất type information.
Constraints giới hạn type parameter phải extend một type cụ thể, ngăn dùng với types không compatible.
// Không có constraint — không access được .length
function getLength<T>(arg: T): number {
return arg.length; // Error!
}
// Có constraint
function getLength<T extends { length: number }>(arg: T): number {
return arg.length; // OK
}
// keyof constraint — type-safe property access
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const name = getProperty({ name: 'An', age: 25 }, 'name'); // string
// Multiple constraints
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}keyof T trả về union type của tất cả keys của T: keyof {a: number, b: string} cho ra 'a' | 'b'.
- Dùng với generics để type-safe property access:
function get<T, K extends keyof T>(obj: T, key: K): T[K]đảm bảo key tồn tại và return đúng kiểu.keyof typeof objlấy keys của object value (không phải type). - Hay gặp trong utility types như
Pick,Record, và khi build form hay table components cần truy cập dynamic property một cách an toàn.
Parameters<T> là utility type trích xuất tuple chứa kiểu của tất cả tham số từ một function type T, ví dụ Parameters<typeof Math.max> cho ra [number, number]. ConstructorParameters<T> hoạt động tương tự nhưng dành cho constructor của class, trả về tuple các kiểu tham số khi khởi tạo đối tượng.
- Bên trong, cả hai đều sử dụng infer keyword trong conditional type: T extends (...args: infer P) => any ? P : never để suy luận ra kiểu tham số.
- Chúng đặc biệt hữu ích khi cần forward arguments sang hàm khác, tạo wrapper functions, hoặc xây dựng higher-order functions mà vẫn giữ nguyên type safety.
- Hàm có thể có nhiều type params.
- Inference hoạt động independently cho mỗi param.
- Đặt tên rõ ràng (TKey, TValue) thay chỉ T, U, V khi có nhiều params.
function zip<T, U>(a: T[], b: U[]): [T, U][] {
return a.map((item, i) => [item, b[i]]);
}
const pairs = zip([1, 2], ['a', 'b']); // [number, string][]
function getEntry<TObj, TKey extends keyof TObj>(obj: TObj, key: TKey): TObj[TKey] {
return obj[key];
}Exclude<T, U> loại bỏ khỏi union T những types có thể assign cho U: Exclude<'a'|'b'|'c', 'a'> cho ra 'b'|'c'. Extract<T, U> ngược lại — giữ lại những types assign được cho U: Extract<string|number|boolean, string|number> cho ra string|number.
Ví dụ thực tế: type NonString<T> = Exclude<T, string> để lọc string ra khỏi union.
- Mẹo nhớ: Exclude = loại ra (exclude từ vocabulary), Extract = trích ra phần giao nhau.
- Nền tảng của
NonNullable<T>chính làExclude<T, null | undefined>.
ReturnType<T> lấy return type của function T mà không cần khai báo lại: type Result = ReturnType<typeof fetchUser> tự động theo dõi khi hàm thay đổi. Awaited<T> unwrap Promise đệ quy: Awaited<Promise<string>> cho ra string, Awaited<Promise<Promise<number>>> cho ra number.
- Kết hợp cả hai cho async functions:
type Data = Awaited<ReturnType<typeof fetchData>>. - Rất hữu ích trong large codebases để tránh type duplication và đảm bảo types luôn đồng bộ với implementation.
NonNullable<T> loại bỏ null và undefined khỏi type T: NonNullable<string | null | undefined> cho ra string.
- Tương đương với
Exclude<T, null | undefined>. - Hay dùng sau khi đã kiểm tra null ở runtime nhưng TypeScript vẫn chưa tự narrow được — ví dụ kết quả từ
Array.find()có typeT | undefined, sau khi guard check có thể cast vềNonNullable. - Với strictNullChecks bật, đây là utility cần thiết để làm việc với optional values an toàn.
Readonly<T> làm tất cả properties của T thành readonly (shallow). ReadonlyArray<T> là array không thể modify (push, pop, sort không available).
Dùng với as const, Redux state, functional programming.
const nums: ReadonlyArray<number> = [1, 2, 3];
nums.push(4); // Error! Property 'push' does not exist
nums[0] = 99; // Error! Index signature is readonly
const state: Readonly<{ count: number }> = { count: 0 };
state.count = 1; // Error!Deep readonly cần custom type: DeepReadonly<T> với recursive mapped type.
Dùng mapped type iterate qua tất cả keys của T, thêm ? để optional.
// Partial<T>: tất cả optional
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// Required<T>: dấu - xóa optional modifier
type MyRequired<T> = {
[K in keyof T]-?: T[K];
};
// Readonly<T>
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// Pick<T, K>
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Record<K, V>
type MyRecord<K extends keyof any, V> = {
[P in K]: V;
};
// Kiểm tra
type User = { id: number; name: string; email?: string };
type PartialUser = MyPartial<User>;
// { id?: number; name?: string; email?: string }Hiểu cách này giúp tạo custom utility types bất kỳ.
Type guards thu hẹp type trong code branch.
Loại: 1) typeof, 2) instanceof, 3) in operator, 4) equality, 5) user-defined (function với return type is Type), 6) assertion functions.
function process(val: string | number | null) {
if (typeof val === 'string') return val.toUpperCase();
if (typeof val === 'number') return val.toFixed(2);
return 'null';
}
// User-defined guard (most powerful)
function isUser(obj: unknown): obj is { id: string; name: string } {
return typeof obj === 'object' && obj !== null && 'id' in obj;
}Function type với return type param is Type.
Khi function return true, TS narrow type của param.
// Type predicate
function isString(val: unknown): val is string {
return typeof val === 'string';
}
// Sử dụng
function process(val: string | number) {
if (isString(val)) {
val.toUpperCase(); // val: string
} else {
val.toFixed(2); // val: number
}
}
// Thực tế: kiểm tra object shape
interface Cat { meow(): void }
interface Dog { bark(): void }
function isCat(animal: Cat | Dog): animal is Cat {
return 'meow' in animal;
}
// Array filter với type guard
const items: (string | null)[] = ['a', null, 'b'];
const strings = items.filter((x): x is string => x !== null);
// strings: string[]Nguy hiểm: TS tin type predicate hoàn toàn, implementation sai sẽ gây runtime bug mà không có compile error.
Declaration files chứa type declarations không có implementation (ambient declarations).
- Dùng để type third-party libraries không viết bằng TS. @types/ packages trên npm.
- Tự viết .d.ts khi cần declare types cho JS code hoặc global variables.
// Khai báo SVG imports (pattern phổ biến với Vite/Webpack)
declare module '*.svg' {
const content: string;
export default content;
}
// Mở rộng global:
declare global {
interface Window { analytics: Analytics; }
}Trong tsconfig.json, strict: true là option quan trọng nhất vì nó bật toàn bộ strict type-checking bao gồm strictNullChecks, noImplicitAny và strictFunctionTypes. target xác định phiên bản ES output (ví dụ ES2020), module chọn hệ thống module (ESNext, CommonJS), và moduleResolution quyết định cách TypeScript tìm kiếm file khi import (node, bundler).
Ngoài ra, paths cho phép cấu hình path aliases (ví dụ @/components), isolatedModules bắt buộc khi dùng bundlers như Vite hoặc esbuild vì chúng compile từng file riêng lẻ, và sourceMap: true giúp debug bằng cách map code đã compile về source TypeScript gốc.
useState: useState<Type>(init) khi init không đủ thông tin. useRef: useRef<HTMLInputElement>(null) cho DOM refs, useRef<number>(0) cho mutable values (không trigger re-render). useReducer: type actions với discriminated unions.
const [user, setUser] = useState<User | null>(null);
const inputRef = useRef<HTMLInputElement>(null); // DOM ref
const timerRef = useRef<number>(0); // mutable value
// Custom hook với explicit return type:
function useCounter(init = 0): [number, () => void] {
const [count, setCount] = useState(init);
return [count, () => setCount(c => c + 1)];
}React.ChangeEvent<HTMLInputElement> cho onChange, React.MouseEvent<HTMLButtonElement> cho onClick, React.FormEvent<HTMLFormElement> cho onSubmit.
Nếu không nhớ chính xác: hover lên event trong IDE, hoặc dùng React.SyntheticEvent rộng hơn.
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setValue(e.target.value);
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
// ...
}paths trong tsconfig.json map import paths để tránh relative paths dài.
Cần configure cả bundler vì TS compiler chỉ handle types, không transform imports.
// tsconfig.json
{ "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
resolve: { alias: { '@': '/src' } }
});
// Dùng: import { Button } from '@/components/Button'Module augmentation cho phép thêm types vào module đã có.
- Dùng để extend third-party types mà không fork.
- Cần import module đó (ambient augmentation vs global augmentation).
- Rất hữu ích cho middleware patterns.
// Thêm user vào Express Request (pattern phổ biến với auth middleware)
declare module 'express' {
interface Request {
user?: { id: string; role: string };
}
}
// Trong route:
app.get('/me', (req, res) => {
res.json(req.user); // TS biết type của user
});TypeScript dùng structural typing (shape giống nhau là tương thích).
Branded types tạo nominal typing để ngăn nhầm lẫn giữa các string types semantically khác nhau (UserId vs Email).
type UserId = string & { readonly _brand: 'UserId' };
type Email = string & { readonly _brand: 'Email' };
// Factory function tạo branded value:
function makeUserId(id: string): UserId { return id as UserId; }
function sendMail(email: Email) { /* ... */ }
const id = makeUserId('abc123');
sendMail(id); // Error! UserId không phải EmailMapped types tạo type mới bằng cách biến đổi properties của type khác: { [K in keyof T]: ... }.
// Giải thích cách hoạt động qua ví dụ
type User = { id: number; name: string; email: string };
// Tương đương Partial<T>
type MyPartial<T> = { [K in keyof T]?: T[K] };
// Tương đương Readonly<T>
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };
// Biến đổi value type
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Xóa modifier: -readonly, -?
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };
// Remap key với as
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};Là nền tảng của nhiều built-in utility types (Partial, Readonly, Record, Pick, Omit).
Conditional types: T extends U ? X : Y.
Evaluate tại compile time dựa vào type relationship.
// Cơ bản
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// Distributive: tự động phân phối trên union
type NonNullable<T> = T extends null | undefined ? never : T;
type C = NonNullable<string | null | undefined>; // string
// Infer: extract type từ bên trong
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type D = ReturnType<() => number>; // number
// Thực tế: flatten array type
type Flatten<T> = T extends Array<infer Item> ? Item : T;
type E = Flatten<string[]>; // string
type F = Flatten<number>; // numberNền tảng của Exclude, Extract, NonNullable, ReturnType, và nhiều utility types.
infer khai báo type variable trong conditional type để 'capture' type được infer.
Chỉ dùng trong extends clause.
// Extract return type của function
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R = ReturnType<() => Promise<string>>; // Promise<string>
// Extract params
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type P = Parameters<(a: string, b: number) => void>; // [string, number]
// Extract element type từ array
type UnwrapArray<T> = T extends (infer U)[] ? U : T;
type U = UnwrapArray<string[]>; // string
// Extract từ Promise
type Awaited<T> = T extends Promise<infer V> ? Awaited<V> : T;
type V = Awaited<Promise<Promise<number>>>; // numberCho phép extract nested types từ complex types một cách type-safe.
Template literal types tạo string types từ combinations.
- Kết hợp với union cực kỳ mạnh cho type-safe event names, CSS properties, API endpoints.
- Intrinsic types: Uppercase, Lowercase, Capitalize, Uncapitalize.
type EventName = `on${Capitalize<string>}`;
type Side = 'top' | 'bottom' | 'left' | 'right';
type Padding = `padding-${Side}`; // "padding-top" | "padding-bottom" | ...
type Handlers = { [K in EventName]?: () => void };
// Dùng trong event bus, styled-components, API path generationRecursive conditional types là kiểu điều kiện tự gọi lại chính nó, cho phép duyệt qua các nested types ở mọi cấp độ, ví dụ: type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T sẽ biến tất cả properties thành optional ở mọi tầng lồng nhau.
- TypeScript giới hạn độ sâu đệ quy (thường khoảng 50 levels) để tránh infinite loop trong quá trình type checking.
- Kỹ thuật này rất hữu ích để xây dựng các utility types phức tạp như DeepReadonly, DeepRequired, hay Flatten để biến đổi cấu trúc dữ liệu lồng nhau một cách type-safe.
DeepPartial được tạo bằng recursive conditional type: nếu T là object thì áp dụng optional modifier cho mỗi key và đệ quy vào giá trị con, ngược lại trả về T nguyên bản (type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T).
- DeepReadonly cần xử lý riêng trường hợp array bằng cách kiểm tra T extends (infer U)[] trước để chuyển thành
ReadonlyArray<DeepReadonly<U>>, sau đó mới xử lý object bằng cách thêm readonly modifier cho tất cả keys và đệ quy tiếp. - Việc tách riêng logic cho array là bắt buộc vì array có semantics khác với object thông thường, nếu không sẽ mất đi các array methods và tính đúng đắn của kiểu dữ liệu.
Decorators là function đặc biệt apply lên class/method/property/accessor/parameter để modify behavior.
- Dùng @decorator syntax.
- TS 5.0+ hỗ trợ TC39 Stage 3 decorators natively (không cần flag).
- Legacy decorators (Angular, NestJS) vẫn dùng experimentalDecorators: true — hai loại không tương thích.
- Decorator factories: @Log() trả về decorator function.
Reflect Metadata API cho phép đọc/ghi metadata trên class/method tại runtime.
- TypeScript emit metadata khi emitDecoratorMetadata: true (design:type, design:paramtypes, design:returntype).
- Dùng bởi dependency injection frameworks (Angular, NestJS) để resolve constructor params tự động.
import 'reflect-metadata';
@Injectable()
class UserService {
constructor(private repo: UserRepository) {}
// NestJS đọc design:paramtypes → tự inject UserRepository
}Function overloads khai báo nhiều function signatures trước implementation.
- Implementation phải compatible với tất cả overloads.
- Dùng khi return type khác nhau tùy input type.
function parse(x: string): number;
function parse(x: number): string;
function parse(x: any): any {
if (typeof x === 'string') return parseInt(x, 10);
return String(x);
}
const n = parse('42'); // n: number
const s = parse(42); // s: stringDistributive conditional types xảy ra khi type parameter T đứng trơn (naked) trong điều kiện extends: TypeScript sẽ tự động tách union ra và áp dụng điều kiện cho từng thành phần, ví dụ (A | B) extends U ? X : Y sẽ thành (A extends U ? X : Y) | (B extends U ? X : Y).
- Hành vi này thường gây kết quả bất ngờ, ví dụ type
IsNever<T>= T extends never ? true : false sẽ trả về never thay vì true khi T = never vì union rỗng không có phần tử nào để distribute. - Để disable distributive behavior, ta wrap type parameter trong tuple: [T] extends [U] ? X : Y, lúc này TypeScript sẽ so sánh toàn bộ union như một khối thay vì phân tách từng thành phần.
Generic components cho phép reuse component với nhiều kiểu data.
- Trong .tsx cần disambiguate
<T,>hoặc<T extends unknown>vì<T>bị hiểu là JSX tag. - TS 5.4+ thêm
NoInfer<T>utility để kiểm soát inference tốt hơn.
function Select<T>({ options, value, onChange }: {
options: T[];
value: T;
onChange: (v: T) => void;
}) {
return <select>...</select>;
}
// Dùng:
<Select<User> options={users} value={selected} onChange={setSelected} />Assertion functions có return type asserts condition hoặc asserts param is Type.
Khi function return (không throw), TS assume assertion đúng và narrow type trong phần code tiếp theo.
function assert(cond: unknown, msg = 'assertion failed'): asserts cond {
if (!cond) throw new Error(msg);
}
function assertIsString(val: unknown): asserts val is string {
if (typeof val !== 'string') throw new TypeError();
}
const val: string | undefined = getValue();
assert(val !== undefined, 'val must exist');
val.toUpperCase(); // OK — TS biết val là string (không còn undefined)Structural typing (duck typing) kiểm tra shape/structure thay tên type. Hai types tương thích nếu có cùng properties. Phù hợp hơn với JavaScript (dynamic, object literal heavy). TypeScript kết hợp: structural làm mặc định + branded types cho nominal khi cần.
Gotcha thú vị: hai class khác tên nhưng cùng shape hoàn toàn có thể assign cho nhau — bất ngờ với devs từ Java/C#.
class Dog { name: string; breed: string; constructor(n: string, b: string) { this.name = n; this.breed = b; } }
class Cat { name: string; breed: string; constructor(n: string, b: string) { this.name = n; this.breed = b; } }
const dog: Dog = new Cat('Luna', 'Siamese'); // OK — same shape!Discriminated unions: prefer khi data-centric, cần exhaustive matching, serializable (Redux actions, API responses).
- Redux Toolkit
createSliceactions là ví dụ thực tế điển hình — mỗi action type là một discriminated union member và reducers exhaustively switch qua chúng. - Class hierarchy: khi cần behavior với data, encapsulation, OOP patterns.
- TypeScript exhaustiveness checking qua never type.
Sau khi handle tất cả cases trong discriminated union, assign default case sang variable type never.
Nếu có case chưa handle, TS báo lỗi.
type Action =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'RESET' };
function reducer(state: number, action: Action): number {
switch (action.type) {
case 'INCREMENT': return state + 1;
case 'DECREMENT': return state - 1;
case 'RESET': return 0;
default:
// Nếu thêm 'SET' vào Action mà không handle, dòng này báo lỗi
const exhaustive: never = action;
throw new Error(`Unhandled action: ${exhaustive}`);
}
}
// Hoặc dùng helper function
function assertNever(x: never): never {
throw new Error('Unexpected value: ' + x);
}Pattern này đảm bảo thêm case mới vào union sẽ bị catch ngay tại compile time.
Type-only imports (import type { Foo } from './foo') chỉ import thông tin kiểu dữ liệu và bị xóa hoàn toàn khỏi code JavaScript sau khi compile, không tạo ra bất kỳ runtime import nào.
- Điều này đặc biệt quan trọng khi bật isolatedModules (bắt buộc với Vite, esbuild) vì các bundler này compile từng file riêng lẻ và không thể biết một import là type hay value.
- Tương tự, export type { Foo } dùng để re-export chỉ type information, giúp ngăn circular dependency issues vì không tạo side effects ở runtime.
- Từ TypeScript 5.0+, ta có thể mix type và value imports trong cùng một statement: import { type Foo, bar } from './module', trong đó chỉ bar được giữ lại sau compile.
Type checking chậm do: complex conditional types, deep recursion, large union types, excessive use of infer. Cải thiện: bật incremental compilation, dùng project references, skipLibCheck: true, tránh deeply recursive types, prefer interface over type cho object shapes (merge tốt hơn), dùng tsc --diagnostics để profile.
Lý do prefer interface cho performance: interface types được cached bởi identity — compiler có thể nhanh chóng xác định hai interface references là cùng một type mà không cần re-evaluate. Type alias (đặc biệt là complex types) phải re-evaluate mỗi lần dùng.