Enum trong TypeScript
Bài 7 – Enum và cách quản lý hằng số trong TypeScript theo cách đơn giản, trực quan và tiêu chuẩn.
Enum trong TypeScript
Enum được dùng để định nghĩa tập hằng số có ý nghĩa, giúp code dễ đọc hơn, giảm sai sót do “magic string” hoặc số khó hiểu. Đây là công cụ quan trọng khi quản lý trạng thái, quyền hạn, loại dữ liệu hoặc config trong dự án.
2. Nội dung chính
- Enum là gì và dùng khi nào
- Numeric Enum và String Enum
- Enum hai chiều (reverse mapping)
- Ứng dụng enum trong trạng thái, phân quyền, loại đối tượng
- Khi nào không nên dùng enum
3. Ví dụ chi tiết
3.1. Numeric Enum
// Numeric Enum tự động đánh số từ 0
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
// Hoặc custom giá trị
enum DirectionWithValue {
Up = 1,
Down = 2,
Left = 3,
Right = 4,
}
// Sử dụng
const move = Direction.Up;
console.log(move); // 0
// Reverse mapping (chỉ numeric enum)
console.log(Direction[0]); // "Up"
console.log(Direction.Up); // 0
3.2. String Enum
// String Enum - recommended cho readability
enum Status {
Success = "SUCCESS",
Error = "ERROR",
Loading = "LOADING",
Pending = "PENDING"
}
function showStatus(status: Status): void {
console.log("Current status:", status);
switch (status) {
case Status.Success:
console.log("✅ Operation completed successfully");
break;
case Status.Error:
console.log("❌ An error occurred");
break;
case Status.Loading:
console.log("⏳ Loading...");
break;
case Status.Pending:
console.log("⏸️ Operation pending");
break;
}
}
// Usage
showStatus(Status.Success);
showStatus(Status.Loading);
3.3. Ứng dụng thực tế: Role-based Access Control
// User roles với String Enum
enum Role {
Admin = "ADMIN",
Staff = "STAFF",
Customer = "CUSTOMER",
Guest = "GUEST"
}
// User type với role
type User = {
id: number;
name: string;
role: Role;
email: string;
};
// Permission checking
function canAccess(user: User, requiredRole: Role): boolean {
const roleHierarchy = {
[Role.Guest]: 0,
[Role.Customer]: 1,
[Role.Staff]: 2,
[Role.Admin]: 3
};
return roleHierarchy[user.role] >= roleHierarchy[requiredRole];
}
function getPermissions(role: Role): string[] {
switch (role) {
case Role.Admin:
return ["read", "write", "delete", "manage_users"];
case Role.Staff:
return ["read", "write"];
case Role.Customer:
return ["read", "update_profile"];
case Role.Guest:
return ["read"];
default:
return [];
}
}
// Usage
const adminUser: User = {
id: 1,
name: "Admin User",
role: Role.Admin,
email: "admin@example.com"
};
console.log(canAccess(adminUser, Role.Staff)); // true
console.log(getPermissions(Role.Admin)); // ["read", "write", "delete", "manage_users"]
3.4. Reverse Mapping (Numeric Enum only)
enum Color {
Red = 1,
Green = 2,
Blue = 3
}
// Forward mapping
console.log(Color.Red); // 1
console.log(Color.Green); // 2
// Reverse mapping - đặc biệt cho numeric enum
console.log(Color[1]); // "Red"
console.log(Color[2]); // "Green"
// Practical usage
function getColorName(colorValue: number): string {
return Color[colorValue] || "Unknown";
}
console.log(getColorName(1)); // "Red"
console.log(getColorName(5)); // "Unknown"
3.5. Heterogeneous Enum (ít dùng)
// Mixed enum - không recommended
enum MixedEnum {
No = 0,
Yes = "YES",
}
console.log(MixedEnum.No); // 0
console.log(MixedEnum.Yes); // "YES"
3. Kiến thức trọng tâm
3.1. Numeric Enum tự động đánh số
- Phù hợp cho cấu hình hệ thống, trạng thái nội bộ, ít cần đọc hiểu trực quan
- Reverse mapping cho phép lookup từ value sang name
- Memory efficient cho large sets
3.2. String Enum dễ đọc và rõ nghĩa hơn
- Nên dùng trong các API, UI, logic phân quyền
- Self-documenting - giá trị có ý nghĩa ngay lập tức
- No reverse mapping - chỉ forward mapping
3.3. Enum giúp loại bỏ “magic string”
- Code sạch hơn, an toàn hơn, tránh lỗi chính tả
- Type safety - compiler catch errors at compile time
- Refactoring dễ dàng - thay đổi ở một chỗ, áp dụng toàn bộ
💡 GHI NHỚ: Luôn dùng String Enum cho user-facing values, Numeric Enum cho internal states
4. Bài tập thực hành
Bài 1: Tạo Orderstatus Enum
enum Orderstatus {
Pending = "PENDING",
Paid = "PAID",
Shipped = "SHIPPED",
Delivered = "DELIVERED",
Cancelled = "CANCELLED"
}
function isPaid(status: Orderstatus): boolean {
return status === Orderstatus.Paid ||
status === Orderstatus.Shipped ||
status === Orderstatus.Delivered;
}
function canCancel(status: Orderstatus): boolean {
return status === Orderstatus.Pending || status === Orderstatus.Paid;
}
// Usage
const orderstatus: Orderstatus = Orderstatus.Paid;
console.log(isPaid(orderstatus)); // true
console.log(canCancel(orderstatus)); // true
Bài 2: Tạo LogLevel Enum
enum LogLevel {
Info = "INFO",
Warn = "WARN",
Error = "ERROR",
Debug = "DEBUG"
}
function log(level: LogLevel, message: string): void {
const timestamp = new Date().toISOString();
const prefix = `[${timestamp}] [${level}]`;
switch (level) {
case LogLevel.Error:
console.error(`${prefix} ❌ ${message}`);
break;
case LogLevel.Warn:
console.warn(`${prefix} ⚠️ ${message}`);
break;
case LogLevel.Info:
console.info(`${prefix} ℹ️ ${message}`);
break;
case LogLevel.Debug:
console.debug(`${prefix} 🐛 ${message}`);
break;
}
}
// Usage
log(LogLevel.Info, "Application started");
log(LogLevel.Error, "Failed to connect to database");
log(LogLevel.Warn, "Deprecated API usage detected");
5. Sai lầm thường gặp
- Dùng enum cho values thay đổi - enum là cho constants
- Quá nhiều enum - đôi khi union types đơn giản hơn
- Numeric enum cho user-facing values - khó debug và maintain
- Quên handle all enum cases trong switch statements
- Dùng heterogeneous enum - không type-safe và confusing
⚠️ GHI NHỚ: Khi enum trở nên quá phức tạp, cân nhắc dùng objects hoặc maps thay thế
6. Kết luận
Enum là giải pháp mạnh để quản lý tập giá trị cố định. Khi dùng đúng, bạn sẽ giảm lỗi logic, tăng tính rõ ràng và chuẩn hóa toàn bộ codebase.
🔑 GHI NHỚ QUAN TRỌNG:
- String Enum: Cho user-facing values, API responses, UI states
- Numeric Enum: Cho internal states, configurations, flags
- Luôn có type safety - compiler sẽ catch lỗi compile-time
- Tránh magic strings - dùng enum cho tất cả constants
- Document intent - enum names nên rõ ràng và self-explanatory