Series Total TypeScript Thực chiến #1: Tuyệt kỹ typeof và keyof – Bắt Runtime "đẻ" ra Type
1. Nỗi đau của việc "Nhại lại code" (Code Duplication)
Khi xây dựng Backend Node.js, chúng ta thường có những object chứa các hằng số (constants) hoặc cấu hình (config).
Ví dụ, bạn định nghĩa các trạng thái của thẻ Metro:
const TICKET_STATUS = {
ACTIVE: 'Đang hoạt động',
LOCKED: 'Bị khóa',
EXPIRED: 'Hết hạn',
};
Bây giờ, bạn muốn viết một hàm nhận vào trạng thái thẻ để xử lý. Theo cách viết thông thường của "người mới", bạn sẽ tự định nghĩa một Type bằng tay:
// ❌ Cực kỳ thủ công và dễ sai sót
type Status = 'ACTIVE' | 'LOCKED' | 'EXPIRED';
function processTicket(status: Status) {
// ...
}
Vấn đề: Nếu ngày mai hệ thống có thêm trạng thái BLACKLISTED vào object TICKET_STATUS, bạn phải nhớ quay lại dòng type Status để cập nhật. Nếu quên, TypeScript sẽ báo lỗi tung tóe, hoặc tệ hơn là lọt bug ra Production.
2. Thuốc giải thứ 1: Cú pháp typeof trong không gian Type
Đừng nhầm lẫn cú pháp này với typeof của JavaScript thuần (dùng để check typeof x === 'string'). Trong TypeScript, khi bạn dùng typeof ở vị trí khai báo Type, nó sẽ đọc cấu trúc của một biến đang chạy (runtime) và trích xuất ra bản thiết kế (Type) của biến đó.
// ✅ Bắt TypeScript tự đọc cấu trúc
type TicketStatusConfig = typeof TICKET_STATUS;
/*
Lúc này, TicketStatusConfig sẽ ngầm định mang cấu trúc:
{
ACTIVE: string;
LOCKED: string;
EXPIRED: string;
}
*/
3. Thuốc giải thứ 2: Lấy chìa khóa bằng keyof
Bây giờ bạn đã có type TicketStatusConfig là một Object. Làm sao để rút trích ra danh sách các keys (chìa khóa) của Object đó thành một Union Type ('ACTIVE' | 'LOCKED' | 'EXPIRED')?
Đó là nhiệm vụ của từ khóa keyof.
type ValidStatusKeys = keyof TicketStatusConfig;
// Kết quả ValidStatusKeys chính là: 'ACTIVE' | 'LOCKED' | 'EXPIRED'
4. Combo hủy diệt: keyof typeof
Trong thực tế, Matt Pocock khuyên chúng ta gộp chung hai bước trên lại thành một dòng duy nhất. Đây là "khẩu quyết" bạn sẽ gõ hàng ngàn lần khi làm việc với TypeScript nâng cao:
const TICKET_STATUS = {
ACTIVE: 'Đang hoạt động',
LOCKED: 'Bị khóa',
EXPIRED: 'Hết hạn',
} as const;
// Lưu ý: Thêm 'as const' để khóa chặt giá trị, báo cho TS biết đây là hằng số không bao giờ đổi.
// 🔥 COMBO HỦY DIỆT
type Status = keyof typeof TICKET_STATUS;
function processTicket(status: Status) {
console.log("Đang xử lý trạng thái:", TICKET_STATUS[status]);
}
processTicket('ACTIVE'); // ✅ Ngon lành
processTicket('PENDING'); // ❌ IDE báo lỗi ngay lập tức vì không tồn tại
5. Tại sao kỹ thuật này định hình tư duy Senior?
- Single Source of Truth (Nguồn chân lý duy nhất): Bạn chỉ định nghĩa dữ liệu ở MỘT nơi duy nhất (biến TICKET_STATUS).
- Tự động cập nhật: Khi bạn thêm một trạng thái mới vào biến JavaScript, hệ thống Type Status sẽ tự động phình to ra để đón nhận cái mới. Bạn không bao giờ phải bảo trì 2 file song song nữa.
- Hoạt động hoàn hảo với Config Object: Rất phù hợp để quản lý Mã lỗi API (Error Codes), Cấu hình cổng kết nối, hoặc Phân quyền Role.
Bằng việc làm chủ keyof typeof, bạn đã xóa bỏ được thói quen viết hard-code tĩnh. Mọi Type giờ đây đều xoay quanh và tự động tiến hóa theo logic của mã nguồn thật.
Ở bài tiếp theo, bạn muốn chúng ta giải quyết bài toán hóc búa hơn là "Móc" Type từ bên trong các phần tử của một Array (Indexed Access Types), hay muốn tìm hiểu cách nhào nặn các chuỗi văn bản bằng Template Literal Types?
All rights reserved