Giới Thiệu
Trong lập trình React, việc quản lý trạng thái là rất quan trọng. Bạn thường xuyên phải xử lý nhiều trạng thái khác nhau như trạng thái của form (đang tải, thành công, lỗi), trạng thái của modal (mở, đóng) hay phản hồi từ API (dữ liệu hoặc lỗi). Một trong những khái niệm quan trọng mà bạn cần hiểu là sự khác biệt giữa union thông thường và union phân biệt trong TypeScript. Hiểu rõ điều này sẽ giúp bạn viết mã an toàn và dễ bảo trì hơn cho các component trong React.
Union Thông Thường Là Gì?
Union thông thường là kiểu dữ liệu có thể chứa nhiều giá trị hoặc kiểu giá trị khác nhau, nhưng TypeScript không liên kết thêm các trường dữ liệu với mỗi giá trị. Ví dụ:
typescript
// Định nghĩa kiểu trạng thái
type Status = "loading" | "success" | "error";
function render(status: Status) {
if (status === "success") {
// ❌ Không thể đính kèm thông tin bổ sung như "data"
return "Form đã được gửi!";
}
return "Chưa gửi";
}
Hạn Chế Của Union Thông Thường
- Không có dữ liệu có cấu trúc cho mỗi trạng thái.
- Không tự động thu hẹp kiểu cho các thuộc tính.
- Chỉ phù hợp cho các cờ đơn giản.
Union Phân Biệt Là Gì?
Union phân biệt là một union của các đối tượng mà mỗi đối tượng có:
- Một thuộc tính phân biệt (thường là một chuỗi hằng như
statushoặctype). - Các trường dữ liệu bổ sung tùy chọn độc nhất cho từng nhánh.
Cách TypeScript Thu Hẹp Kiểu
typescript
// Định nghĩa kiểu trạng thái form
type FormState =
| { status: "loading" }
| { status: "success"; data: string }
| { status: "error"; message: string };
function renderForm(state: FormState) {
if (state.status === "loading") return <p>Đang tải...</p>;
if (state.status === "success") return <p>Đã gửi: {state.data}</p>;
if (state.status === "error") return <p>Lỗi: {state.message}</p>;
}
- TypeScript tự động thu hẹp kiểu của
statedựa trên thuộc tính phân biệt (status). - Mỗi nhánh chỉ có quyền truy cập vào các trường liên quan (data, message).
So Sánh Union Thông Thường và Union Phân Biệt
Không Có Union Phân Biệt
typescript
// Không có union phân biệt
type FormState = "loading" | "success" | "error";
function renderForm(state: FormState) {
if (state === "success") {
// ❌ Không thể đính kèm thông tin bổ sung như data
return "Form đã được gửi!";
}
}
Có Union Phân Biệt
typescript
// Với union phân biệt
type FormState =
| { status: "loading" }
| { status: "success"; data: string }
| { status: "error"; message: string };
function renderForm(state: FormState) {
if (state.status === "success") {
// ✅ TypeScript biết 'data' tồn tại
return `Form đã được gửi với: ${state.data}`;
}
}
Các Trường Hợp Sử Dụng Thực Tế Trong React
Ví Dụ Về Trạng Thái Form
typescript
// Định nghĩa trạng thái cho form
type FormState =
| { status: "idle" }
| { status: "submitting" }
| { status: "success"; result: string }
| { status: "error"; message: string };
function Form({ state }: { state: FormState }) {
switch (state.status) {
case "idle": return <button>Gửi</button>;
case "submitting": return <p>Đang gửi...</p>;
case "success": return <p>{state.result}</p>;
case "error": return <p>{state.message}</p>;
}
}
Ví Dụ Về Modal
typescript
// Định nghĩa trạng thái cho modal
type ModalState =
| { open: true; content: string }
| { open: false };
function Modal({ state }: { state: ModalState }) {
if (!state.open) return null;
return <div>{state.content}</div>;
}
Kết Luận
Union thông thường có thể đủ cho các cờ đơn giản, nhưng chúng không thể mang theo dữ liệu cho từng trường hợp. Ngược lại, union phân biệt cho phép TypeScript biết chính xác các trường nào tồn tại cho từng trường hợp, làm cho các component trở nên an toàn hơn. Đối với các ứng dụng React, luôn ưu tiên sử dụng union phân biệt cho trạng thái component, phản hồi từ API và các thuộc tính loại trừ lẫn nhau.
Thực Hành Tốt Nhất
- Luôn sử dụng union phân biệt khi thiết kế trạng thái cho các component.
- Đảm bảo rằng các thuộc tính phân biệt rõ ràng và dễ hiểu.
Các Cạm Bẫy Thông Thường
- Không sử dụng union thông thường cho các trường hợp cần dữ liệu có cấu trúc.
- Không quên kiểm tra kỹ các thuộc tính trong mỗi nhánh.
Câu Hỏi Thường Gặp (FAQ)
1. Union phân biệt có thể được sử dụng trong các tình huống nào?
Union phân biệt rất hữu ích trong các tình huống mà bạn cần xác định trạng thái của một component hoặc phản hồi từ API.
2. Có cách nào khác để quản lý trạng thái trong React không?
Có, bạn có thể sử dụng các thư viện như Redux hoặc Context API để quản lý trạng thái, nhưng union phân biệt thường mang lại lợi ích rõ ràng hơn khi bạn muốn làm rõ cấu trúc dữ liệu.
3. Tại sao TypeScript lại tự động thu hẹp kiểu?
TypeScript sử dụng thuộc tính phân biệt để xác định kiểu dữ liệu chính xác, giúp giảm thiểu lỗi và tăng cường bảo trì.