Giới thiệu
Khi làm việc với TypeScript, các lập trình viên thường cần đại diện cho một tập hợp các hằng số liên quan. Mặc dù các enum truyền thống là lựa chọn rõ ràng, TypeScript cung cấp nhiều phương pháp khác nhau, mỗi phương pháp có những ưu điểm và nhược điểm riêng. Trong bài viết này, chúng ta sẽ khám phá ba phương pháp chính: const enum, enum thông thường và object với as const, giúp bạn đưa ra quyết định thông minh cho dự án của mình.
1. Enum thông thường (enum)
Định nghĩa: Enum truyền thống tồn tại cả trong thời gian biên dịch và thời gian chạy.
typescript
export enum UserRole {
Admin = "ADMIN",
User = "USER",
Guest = "GUEST"
}
Kết quả biên dịch:
typescript
export var UserRole;
(function (UserRole) {
UserRole["Admin"] = "ADMIN";
UserRole["User"] = "USER";
UserRole["Guest"] = "GUEST";
})(UserRole || (UserRole = {}));
Ưu điểm:
- ✅ Tồn tại tại thời gian chạy: Đối tượng đầy đủ có sẵn trong quá trình thực thi
- ✅ Hỗ trợ phản chiếu: Có thể lặp lại, xác thực và kiểm tra các giá trị
- ✅ Tính tương thích giữa các module: Hoạt động liền mạch qua các ranh giới tệp
- ✅ Cú pháp quen thuộc: Hành vi enum truyền thống từ các ngôn ngữ khác
Nhược điểm:
- ❌ Kích thước gói lớn hơn: Tạo mã JavaScript bổ sung
- ❌ Khả năng loại bỏ cây kém: Khó loại bỏ các giá trị không sử dụng
- ❌ Chi phí hiệu suất: Tạo đối tượng và truy cập thuộc tính
Phù hợp nhất cho: API công khai, xác thực tại thời gian chạy và các tình huống cần phản chiếu.
2. Const Enums (const enum)
Định nghĩa: Enum chỉ tồn tại tại thời gian biên dịch và hoàn toàn bị xóa khỏi đầu ra JavaScript.
typescript
export const enum HttpStatus {
OK = 200,
Created = 201,
NotFound = 404,
ServerError = 500
}
Kết quả biên dịch: Các giá trị được nhúng trực tiếp nơi chúng được sử dụng:
typescript
// Thay vì HttpStatus.OK, bạn có:
const status = 200;
Ưu điểm:
- ✅ Không có chi phí tại thời gian chạy: Không có mã JavaScript được tạo ra
- ✅ Hiệu suất tuyệt vời: Các giá trị được nhúng trực tiếp
- ✅ Khả năng loại bỏ cây hoàn hảo: Không còn mã không sử dụng
- ✅ Kích thước gói nhỏ: Ảnh hưởng tối thiểu đến bản dựng cuối cùng
Nhược điểm:
- ❌ Không có quyền truy cập tại thời gian chạy: Không thể lặp lại hoặc phản chiếu
- ❌ Vấn đề ranh giới module: Vấn đề với biên dịch độc lập
- ❌ Thách thức trong gỡ lỗi: Tên enum gốc có thể bị mất
Phù hợp nhất cho: Các hằng số nội bộ, mã nhạy cảm với hiệu suất và ánh xạ giá trị đơn giản.
3. Object với as const
Định nghĩa: Phương pháp hiện đại của TypeScript sử dụng các đối tượng đơn giản với các khẳng định const.
typescript
export const UserRole = {
Admin: "ADMIN",
User: "USER",
Guest: "GUEST"
} as const;
export type UserRole = typeof UserRole[keyof typeof UserRole];
Kết quả biên dịch: (Giống như đầu vào)
typescript
export const UserRole = {
Admin: "ADMIN",
User: "USER",
Guest: "GUEST"
};
Ưu điểm:
- ✅ Không có chi phí tại thời gian chạy: Đối tượng đơn giản, không có mã phát sinh thêm
- ✅ An toàn kiểu hoàn toàn: Tích hợp đầy đủ với TypeScript
- ✅ Quyền truy cập tại thời gian chạy: Đối tượng đầy đủ có sẵn để phản chiếu
- ✅ Mẫu hiện đại: Phù hợp với sự phát triển của TypeScript
- ✅ Giá trị linh hoạt: Có thể kết hợp các kiểu (chuỗi, số, v.v.)
Nhược điểm:
- ❌ Trích xuất kiểu thủ công: Cần định nghĩa kiểu bổ sung
- ❌ Không có ánh xạ ngược tích hợp: Khác với các enum số
- ❌ Có phần dài dòng hơn: Cần hai khai báo
Phù hợp nhất cho: Các mã nguồn hiện đại, các kiểu giá trị hỗn hợp và khi cần cả quyền truy cập tại thời gian biên dịch lẫn thời gian chạy.
Phân tích so sánh
Tác động đến kích thước gói
typescript
// enum: ~150 bytes được tạo
enum Colors { Red, Green, Blue }
// const enum: 0 bytes được tạo
const enum Colors { Red, Green, Blue }
// object as const: ~50 bytes (chỉ đối tượng)
const Colors = { Red: 0, Green: 1, Blue: 2 } as const
Hiệu suất tại thời gian chạy
typescript
// enum: Truy cập thuộc tính đối tượng
const color = Colors.Red;
// const enum: Giá trị nhúng trực tiếp (nhanh nhất)
const color = 0;
// object as const: Truy cập thuộc tính đối tượng
const color = Colors.Red;
An toàn kiểu
Cả ba phương pháp đều cung cấp an toàn kiểu tuyệt vời, nhưng object as const cung cấp sự linh hoạt nhất với các kiểu hỗn hợp:
typescript
// Chỉ có thể với object as const
const Status = {
Active: "ACTIVE",
Pending: 0,
Disabled: "DISABLED"
} as const;
Ví dụ thực tiễn
Tình huống 1: Xử lý phản hồi API
typescript
// Tốt nhất: object as const (cần xác thực tại thời gian chạy)
export const ApiErrorCode = {
NotFound: "NOT_FOUND",
Unauthorized: "UNAUTHORIZED",
ServerError: "SERVER_ERROR"
} as const;
export type ApiErrorCode = typeof ApiErrorCode[keyof typeof ApiErrorCode];
function handleError(code: ApiErrorCode) {
if (Object.values(ApiErrorCode).includes(code)) {
// Mã lỗi hợp lệ
}
}
Tình huống 2: Hằng số nội bộ
typescript
// Tốt nhất: const enum (chỉ tại thời gian biên dịch)
export const enum Direction {
Up = 1,
Down = 2,
Left = 3,
Right = 4
}
function move(direction: Direction) {
// Các giá trị được nhúng: direction === 1, v.v.
}
Tình huống 3: API thư viện công khai
typescript
// Tốt nhất: enum thông thường (tính tương thích tại thời gian chạy)
export enum LogLevel {
Error = 0,
Warn = 1,
Info = 2,
Debug = 3
}
// Người tiêu dùng có thể lặp lại hoặc xác thực
console.log(Object.values(LogLevel));
Ví dụ di chuyển
Từ enum truyền thống:
typescript
enum OldStyle {
Value1 = "VALUE1",
Value2 = "VALUE2"
}
Đến object as const hiện đại:
typescript
const NewStyle = {
Value1: "VALUE1",
Value2: "VALUE2"
} as const;
type NewStyle = typeof NewStyle[keyof typeof NewStyle];
Hướng dẫn quyết định
| Trường hợp sử dụng | Phương pháp được khuyến nghị |
|---|---|
| API thư viện công khai | enum |
| Mã nhạy cảm với hiệu suất | const enum |
| Xác thực tại thời gian chạy | object as const |
| Các kiểu giá trị hỗn hợp | object as const |
| Mã nguồn cũ | enum (tính nhất quán) |
| Dự án mới | object as const |
| Sử dụng giữa các module | enum hoặc object as const |
Thực tiễn tốt nhất
- Sử dụng
object as constcho hầu hết các phát triển mới - Giữ
const enumcho các hằng số nhạy cảm với hiệu suất - Chọn
enumkhi xuất bản thư viện cho người tiêu dùng rộng rãi - Xem xét công cụ: Một số công cụ xây dựng xử lý
const enumkém - Giữ nhất quán trong mã nguồn của bạn
Kết luận
TypeScript cung cấp nhiều cách để xử lý các hằng số, mỗi cách phục vụ cho những nhu cầu khác nhau:
enum: Lựa chọn truyền thống cho quyền truy cập tại thời gian chạy và tính tương thíchconst enum: Tối ưu hóa hiệu suất cho các giá trị chỉ tại thời gian biên dịchobject as const: Cách tiếp cận hiện đại, linh hoạt cho hầu hết các trường hợp sử dụng
Hiểu rõ những tùy chọn này cho phép bạn viết mã TypeScript hiệu quả và dễ bảo trì hơn. Đối với hầu hết các ứng dụng hiện đại, object as const cung cấp sự cân bằng tốt nhất giữa an toàn kiểu, quyền truy cập tại thời gian chạy và hiệu quả kích thước gói, trong khi const enum vẫn có giá trị cho các tình huống nhạy cảm với hiệu suất và enum giữ vị trí của nó trong các API công khai và mã nguồn cũ.
Hãy chọn dựa trên nhu cầu cụ thể của bạn, và nhớ rằng tính nhất quán trong một dự án thường quan trọng hơn sự tối ưu hoàn hảo.