TypeScript cơ bản – Null, undefined, narrowing và type guards
- 17-10-2026
- Toanngo92
- 0 Comments
Một biến mang union type không cho phép gọi mọi phương thức của các kiểu thành viên. TypeScript cần bằng chứng về giá trị tại vị trí đang dùng. Narrowing là quá trình thu hẹp kiểu dựa trên luồng điều khiển, chẳng hạn điều kiện if hoặc switch. Đây là cách xử lý dữ liệu an toàn hơn việc chuyển mọi thứ thành any.
Mục lục
1. Phân biệt null và undefined
Undefined thường xuất hiện khi chưa có giá trị, truy cập thuộc tính không tồn tại hoặc hàm không trả kết quả. Null thường được dùng để biểu thị rõ trạng thái không có dữ liệu. Cách quy ước tùy ứng dụng, nhưng khi bật strictNullChecks thông qua strict, cả hai cần được xét riêng trước khi dùng như một string hay object chắc chắn tồn tại.
function displayName(value: string | null | undefined): string {
return value?.trim() || "Khách";
}
console.log(displayName(null)); // Khách
console.log(displayName(" An ")); // An
Ví dụ dùng || vì muốn thay cả chuỗi rỗng sau trim. Nếu cần giữ chuỗi rỗng, số 0 hoặc false, hãy cân nhắc ??; toán tử này chỉ thay null và undefined. Optional chaining ?. dừng truy cập khi gặp null/undefined tại vị trí được kiểm tra, không xác thực cấu trúc của một JSON bất kỳ.
2. Thu hẹp bằng typeof và kiểm tra giá trị
function formatId(id: string | number): string {
if (typeof id === "string") {
return id.trim().toUpperCase();
}
return id.toFixed(0);
}
Sau khi nhánh string đã return, phần còn lại chỉ xử lý number. TypeScript theo dõi luồng này nên không cần ép kiểu. Có thể dùng instanceof cho instance của class và toán tử in để kiểm tra sự tồn tại của thuộc tính; tuy nhiên một thuộc tính có tồn tại chưa đồng nghĩa giá trị của nó đúng kiểu.
Khi kiểm tra object, nhớ rằng typeof null === "object". Vì vậy typeof value === "object" cần kết hợp value !== null trước khi truy cập thuộc tính. Cũng đừng dùng if (value) nếu bạn cần giữ lại 0, false hoặc chuỗi rỗng hợp lệ.
3. Discriminated union cho các trạng thái
type LoadState =
| { status: "loading" }
| { status: "success"; title: string }
| { status: "error"; message: string };
function describe(state: LoadState): string {
switch (state.status) {
case "loading": return "Đang tải";
case "success": return state.title;
case "error": return state.message;
default: {
const impossible: never = state;
return impossible;
}
}
}
Status là thuộc tính phân biệt: từng giá trị literal gắn với một cấu trúc tương ứng. Chỉ nhánh success có title; chỉ nhánh error có message. Nhánh default gán vào never để trình biên dịch nhắc khi bạn thêm một trạng thái nhưng quên xử lý. Không cần khai báo một object có tất cả trường optional rồi đoán trường nào hiện có.
4. Type guard tự viết cho unknown
interface Student { name: string; }
function isStudent(value: unknown): value is Student {
return typeof value === "object" && value !== null &&
"name" in value && typeof value.name === "string";
}
const payload: unknown = { name: "An" };
if (isStudent(payload)) {
console.log(payload.name.toUpperCase()); // AN
}
Phần value is Student là type predicate. Khi hàm trả true, TypeScript tin rằng value là Student. Vì vậy logic bên trong phải kiểm tra đủ các trường mà Student yêu cầu; viết một hàm luôn trả true sẽ làm mất ý nghĩa kiểm tra. Dữ liệu lồng nhau, mảng và quy tắc nghiệp vụ cũng cần được kiểm tra nếu kiểu của bạn có chúng.
5. Vì sao không dùng as hoặc dấu ! để bỏ lỗi?
as Student và non-null assertion ! không tạo kiểm tra khi chạy. Chúng thể hiện lời khẳng định của lập trình viên với trình biên dịch; nếu khẳng định sai, ứng dụng vẫn lỗi. Hãy ưu tiên điều kiện kiểm tra hoặc trả về sớm. Chạy các ví dụ với cấu hình strict ở bài đầu; giữ dòng export {} cuối file để cô lập phạm vi.
Đọc thêm: TypeScript: Narrowing.
Đọc tiếp trong loạt bài
Bài trước: TypeScript cơ bản – Type và interface: mô tả cấu trúc dữ liệu
Bài tiếp theo: TypeScript cơ bản – Generics và keyof: tái sử dụng mà vẫn giữ kiểu
