TypeScript ngày càng trở thành lựa chọn hàng đầu của lập trình viên front-end và back-end. Không chỉ giúp phát hiện lỗi sớm khi compile, TypeScript còn giúp bạn viết code “tự bảo vệ mình” — dễ đọc, dễ mở rộng, và ít bug hơn.
Nhưng giữa vô vàn cú pháp và type utility có sẵn, bạn có chắc mình đang tận dụng TypeScript đúng cách?
Trong bài viết này, chúng ta sẽ cùng khám phá 10 tip hữu ích mà ngay cả dev có kinh nghiệm đôi khi cũng bỏ qua.
Các tip này mình đã tổng hợp từ kinh nghiệm thực tế khi code React, Node.js, và cả khi xây dựng SDK cho API.
Tip 1: Dùng as const để cố định literal type
Giả sử bạn có một danh sách vai trò người dùng:
const roles = ['admin', 'user', 'guest'];
Khi đó, TypeScript hiểu roles là string[] — nghĩa là các phần tử chỉ là “chuỗi bất kỳ”.
Nhưng bạn muốn chúng là cố định, ví dụ chỉ có ba lựa chọn trên.
Khi thêm as const:
const roles = ['admin', 'user', 'guest'] as const; type Role = typeof roles[number]; // "admin" | "user" | "guest"
Giờ đây, biến roles đã trở thành tuple bất biến, và Role là union type gồm ba giá trị literal.
Lợi ích: Tránh lỗi khi truyền tham số sai, đặc biệt khi dùng constant trong enum logic hoặc dropdown list.
Tip 2: Tạo type từ object với keyof typeof
Khi bạn có một object cấu hình:
const config = { dev: 'localhost', prod: 'myapp.com', };
Bạn muốn type Env chỉ bao gồm 'dev' | 'prod'.
Rất dễ:
type Env = keyof typeof config; // 'dev' | 'prod'
Kỹ thuật này cực hữu ích khi bạn cần đồng bộ giữa object thực tế và type logic.
Nếu sau này bạn thêm staging vào object, type sẽ tự động cập nhật mà không cần sửa tay.
Ứng dụng: Dùng trong config môi trường, mapping route, hoặc define API endpoint constants.
Tip 3: Dùng Partial<T> khi cập nhật dữ liệu
Trong ứng dụng, bạn thường có type User như sau:
type User = { name: string; age: number; email: string; };
Khi cập nhật user, bạn chỉ muốn truyền vài field.
Thay vì tạo type mới, bạn dùng Partial:
function updateUser(user: User, patch: Partial<User>) { return { ...user, ...patch }; }
Partial<T> giúp biến tất cả thuộc tính của T thành tùy chọn (optional).
Mẹo: Kết hợp Partial và Pick để chỉ cập nhật một nhóm field nhất định.
Tip 4: Tái sử dụng type với Pick và Omit
Thay vì tạo nhiều type gần giống nhau, bạn có thể tái sử dụng type gốc.
type User = { id: number; name: string; email: string; password: string; };
Tạo type chỉ hiển thị thông tin công khai:
type PublicUser = Omit<User, 'password'>;
Hoặc chỉ chọn vài field:
type UserPreview = Pick<User, 'id' | 'name'>;
Pick và Omit giúp bạn tránh duplication, dễ refactor, và giữ type luôn đồng bộ với entity gốc.
Tip 5: Dùng Union Discriminated để tận dụng type guard mạnh mẽ
Giả sử bạn có nhiều “shape” khác nhau:
type Shape = | { kind: 'circle'; radius: number } | { kind: 'square'; size: number };
Giờ, khi viết hàm area, bạn chỉ cần check kind:
function area(shape: Shape) { if (shape.kind === 'circle') { return Math.PI * shape.radius ** 2; } return shape.size ** 2; }
TypeScript sẽ tự động suy luận rằng nếu kind === 'circle', thì shape chắc chắn có radius.
Lợi ích: Không cần ép kiểu, không lỗi runtime, autocomplete chính xác.
Thường dùng trong React reducer hoặc state machine pattern.
Tip 6: Dùng Record<K,V> để định nghĩa map-type
Thay vì khai báo object thủ công, Record giúp bạn định nghĩa key-value type rõ ràng.
type RoleMap = Record<'admin' | 'user' | 'guest', string>; const roleDescription: RoleMap = { admin: 'Quản trị hệ thống', user: 'Người dùng thường', guest: 'Khách truy cập', };
Record cực kỳ hữu ích khi bạn muốn map các key cố định sang giá trị (ví dụ: code -> label, status -> color…).
Tip 7: Generics giúp function linh hoạt và mạnh hơn
Generics là một trong những phần “ngầu” nhất của TypeScript.
Ví dụ đơn giản:
function identity<T>(value: T): T { return value; }
Giờ T có thể là bất kỳ kiểu nào mà vẫn được giữ nguyên type.
Hoặc bạn có thể thêm ràng buộc:
function merge<T extends object, U extends object>(a: T, b: U) { return { ...a, ...b }; }
Khi gọi merge({ name: 'An' }, { age: 30 }),
TypeScript hiểu kết quả là { name: string; age: number }.
Generics giúp bạn viết function tái sử dụng, đặc biệt khi làm việc với API, form, hoặc component React.
Tip 8: never - type quan trọng bị quên lãng
never được dùng để đánh dấu giá trị không bao giờ xảy ra.
Một ví dụ trong switch-case:
type Status = 'success' | 'error'; function handleStatus(status: Status) { switch (status) { case 'success': console.log('OK'); break; case 'error': console.log('Error'); break; default: const _exhaustiveCheck: never = status; throw new Error(`Unhandled status: ${_exhaustiveCheck}`); } }
Nếu bạn thêm một giá trị mới vào Status (ví dụ 'pending') mà quên xử lý,
TypeScript sẽ báo lỗi ngay lập tức ở _exhaustiveCheck.
Đây là cách kiểm tra “tính toàn vẹn” của union type trong compile-time, cực hữu ích cho logic phức tạp.
Tip 9: Dấu chấm than ! (Non-null assertion)
Đôi khi bạn chắc chắn rằng một giá trị sẽ tồn tại, nhưng TypeScript không hiểu điều đó.
const el = document.querySelector('#app')!; el.innerHTML = 'Xin chào!';
Dấu ! giúp bạn bỏ qua cảnh báo null, nhưng cần cẩn thận — nếu phần tử không tồn tại thật, bạn sẽ gặp lỗi runtime.
Chỉ nên dùng khi bạn 100% chắc chắn giá trị đó không phải null hoặc undefined.
Tip 10: Dùng satisfies (TypeScript 4.9+) để xác thực cấu trúc object
Từ TS 4.9, bạn có thể dùng từ khóa satisfies để đảm bảo object đúng cấu trúc nhưng vẫn giữ literal type.
const config = { port: 3000, mode: 'dev', } satisfies { port: number; mode: 'dev' | 'prod' };
Khi đó:
-
config.mode vẫn có literal type 'dev'
-
Nhưng nếu bạn gõ sai key hoặc giá trị, TS sẽ báo lỗi ngay.
satisfies là sự kết hợp hoàn hảo giữa as và strict type checking.
Bonus Tip: Kết hợp các utility type để “tái chế” code
Bạn có thể xây dựng type phức tạp từ những mảnh nhỏ:
type User = { id: number; name: string; email: string; password: string; }; type SafeUser = Omit<User, 'password'>; type OptionalUser = Partial<SafeUser>;
Hoặc tạo helper type:
type ReadonlyRecord<K extends string, V> = Readonly<Record<K, V>>;
Hãy nghĩ TypeScript như một ngôn ngữ logic mô tả cấu trúc dữ liệu, chứ không chỉ là “kiểm tra lỗi cú pháp”.
Kết luận
TypeScript không chỉ là “JavaScript có type” — mà là công cụ giúp bạn nghĩ rõ ràng hơn về dữ liệu và hành vi.
Khi tận dụng các tip như as const, Partial, Record, hay satisfies, bạn sẽ nhận ra mình viết code ít hơn nhưng an toàn hơn.