10 TypeScript Pattern giúp code sạch hơn
Tổng hợp 10 TypeScript pattern thực tiễn — từ discriminated unions, template literal types đến satisfies operator — giúp code type-safe và dễ bảo trì.
TypeScript không chỉ là JavaScript với type annotations. Được dùng đúng cách, TypeScript giúp bắt lỗi tại compile time, cải thiện developer experience và làm cho codebase dễ refactor hơn. Đây là 10 pattern mà đội ngũ Ventra Rocket sử dụng hàng ngày.
1. Discriminated Unions thay vì Boolean Flags
Thay vì dùng nhiều boolean flags dễ gây state không hợp lệ:
// Tệ: Có thể có state không hợp lệ như isLoading=true và error="..."
interface DataState {
isLoading: boolean;
data?: User[];
error?: string;
}
// Tốt: Discriminated union đảm bảo state luôn hợp lệ
type DataState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User[] }
| { status: 'error'; error: string };
function renderState(state: DataState) {
switch (state.status) {
case 'loading': return <Spinner />;
case 'success': return <UserList users={state.data} />; // data đảm bảo tồn tại
case 'error': return <ErrorView message={state.error} />;
default: return null;
}
}
2. Template Literal Types cho Type-Safe Strings
type EventName = `on${Capitalize<string>}`;
type CSSProperty = `${string}-${string}`;
type Locale = 'vi' | 'en';
type LocalizedKey = `${string}.${Locale}`;
// API endpoint typing
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
type ApiRoute = `/api/v${number}/${string}`;
function request(method: HttpMethod, url: ApiRoute) { /* ... */ }
request('GET', '/api/v1/users'); // OK
request('GET', '/v1/users'); // Error: not /api/v...
3. Satisfies Operator (TS 4.9+)
// satisfies giữ nguyên literal type nhưng vẫn kiểm tra với interface
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
} satisfies Record<string, string | number>;
// typeof config.apiUrl === 'string' (không phải 'https://api.example.com')
// nhưng đảm bảo tất cả values đều là string | number
const palette = {
primary: '#06d6a0',
secondary: '#8b5cf6',
} satisfies Record<string, `#${string}`>;
// Error nếu value không phải hex color
4. Infer trong Conditional Types
// Extract return type của async function
type Awaited<T> = T extends Promise<infer U> ? U : T;
// Extract props type từ React component
type ComponentProps<T> = T extends React.ComponentType<infer P> ? P : never;
// Flatten nested array
type Flatten<T> = T extends Array<infer Item> ? Item : T;
type Nums = Flatten<number[][]>; // number[]
// Extract route params từ path string
type RouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param | keyof RouteParams<Rest>]: string }
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
type UserRoute = RouteParams<'/users/:userId/posts/:postId'>;
// { userId: string; postId: string }
5. Builder Pattern với Method Chaining
class QueryBuilder<T> {
private filters: Partial<T>[] = [];
private sortField?: keyof T;
private limitValue = 50;
where(filter: Partial<T>): this {
this.filters.push(filter);
return this;
}
orderBy(field: keyof T): this {
this.sortField = field;
return this;
}
limit(n: number): this {
this.limitValue = n;
return this;
}
build(): string {
// Build SQL/API query
return JSON.stringify({ filters: this.filters, sort: this.sortField, limit: this.limitValue });
}
}
const query = new QueryBuilder<User>()
.where({ role: 'admin' })
.orderBy('createdAt')
.limit(20)
.build();
6. Readonly và Immutability
// Ngăn mutation ngoài ý muốn
type ImmutableUser = Readonly<{
id: string;
profile: Readonly<{
name: string;
email: string;
}>;
permissions: ReadonlyArray<string>;
}>;
// Deep readonly helper
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
7. Function Overloads cho API rõ ràng
function parseInput(input: string): number;
function parseInput(input: number): string;
function parseInput(input: string | number): string | number {
if (typeof input === 'string') return parseInt(input, 10);
return input.toString();
}
const num = parseInput('42'); // TypeScript biết return là number
const str = parseInput(42); // TypeScript biết return là string
8. Mapped Types cho Object Transformation
// Tất cả keys thành optional
type Partial<T> = { [K in keyof T]?: T[K] };
// Tất cả values thành array
type Arrayify<T> = { [K in keyof T]: T[K][] };
// Chỉ lấy method keys
type MethodKeys<T> = {
[K in keyof T]: T[K] extends (...args: unknown[]) => unknown ? K : never
}[keyof T];
// Tạo event map từ state type
type StateEventMap<T> = {
[K in keyof T as `${string & K}Changed`]: (newValue: T[K]) => void;
};
9. Branded Types cho Domain Primitives
// Ngăn nhầm lẫn giữa các string ID có cùng kiểu dữ liệu
type Brand<T, B extends string> = T & { readonly __brand: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
type ProductId = Brand<string, 'ProductId'>;
function createUserId(id: string): UserId {
return id as UserId;
}
function getUser(id: UserId) { /* ... */ }
function getOrder(id: OrderId) { /* ... */ }
const userId = createUserId('user-123');
getUser(userId); // OK
getOrder(userId); // Error: UserId không phải OrderId
10. const Assertions và as const
// as const giữ literal types thay vì widening
const ROUTES = {
home: '/',
about: '/about',
blog: '/blog',
contact: '/contact',
} as const;
type Route = typeof ROUTES[keyof typeof ROUTES];
// '/' | '/about' | '/blog' | '/contact'
// Tuple types chính xác
function createPoint(x: number, y: number) {
return [x, y] as const; // readonly [number, number] thay vì number[]
}
Kết luận
TypeScript mạnh nhất khi bạn để compiler làm việc cho mình. Discriminated unions loại bỏ null checks, branded types ngăn logic bugs, và template literal types đưa runtime errors về compile time. Đầu tư thời gian học các pattern này sẽ giúp codebase của bạn ít bugs hơn và dễ refactor hơn đáng kể.
Bài viết liên quan
Next.js App Router: Patterns cho Production
Hướng dẫn thực chiến xây dựng web app nhanh với Next.js App Router — server components, streaming, parallel routes, caching strategies.
Quản lý State trong React 2026: Zustand, TanStack Query và URL State
Chọn đúng công cụ quản lý state trong React — TanStack Query cho server state, Zustand cho global UI state, URL cho filter, useState cho local state.
Bảo mật Web: OWASP Top 10 cho Node.js và React
Hướng dẫn bảo mật thực chiến — ngăn chặn SQL injection, XSS, broken authentication, IDOR và security misconfiguration.