Trick or Type Guard? Sống sót trước `unknown` cùng is-kit 🎃
Hoi hoi! 👋
Mình là @nyaomaru, một frontend engineer cực kỳ sợ những thứ kinh dị. 🙀
Chúc mừng Halloween! 🎃👻
Tối nay, chúng ta sẽ khám phá một dự án TypeScript bị ma ám.
Có zombie 🧟, ma 👻, những quả bí ngô bí ẩn 🎃 và một ma cà rồng đáng sợ 🧛 đang chờ đón chúng ta.
Và tất cả đều liên quan đến type trong TypeScript!
Nhiệm vụ của chúng ta?
Sống sót qua đêm mà không đánh mất type safety! 😸
Đi thôi!

🧟 Chương 1: Xác sống không rõ danh tính
Một đêm tối tăm và rùng rợn.
Bỗng có thứ gì đó bò lên từ ngôi mộ! 🧟
const creature: unknown = JSON.parse('{"message":"BRAAAINS!"}');
Ôi! Một sinh vật chưa rõ là gì!
Thử xem nó muốn gì nào.
console.log(creature.message);
// ❌ 'creature' is of type 'unknown'.
GÌ CƠ?! 🙀
TypeScript không cho phép!
Nhưng tại sao?
Vì thực ra chúng ta chưa biết creature là gì.
Nó có thể là zombie. Có thể là ma. Thậm chí có thể là một bé mèo dễ thương. 😸
unknown có nghĩa là chúng ta chưa xác minh giá trị đó.
Không giống any, TypeScript không cho phép truy cập tùy ý các thuộc tính trên giá trị unknown.
Và đó thực ra là điều tốt!
Thử một trường hợp đơn giản hơn nhé.
import { isString } from "is-kit";
const voice: unknown = JSON.parse('"BRAAAINS!"');
if (isString(voice)) {
console.log(voice.toUpperCase());
// BRAAAINS!
}
YAY! 🎉
Chúng ta đã sống sót lần gặp đầu tiên!
Tất nhiên, ở đây dùng typeof voice === 'string' cũng hoàn toàn được.
Với một phép kiểm tra đơn lẻ, như vậy là đủ!
Nhưng nếu quái vật của chúng ta ngày càng lớn và phức tạp thì sao?
🎃 Chương 2: Quả bí ngô unknown khổng lồ
Nhìn kìa!
MỘT QUẢ BÍ NGÔ KHỔNG LỒ! 🎃
Nghe nói bên trong chứa đầy kẹo.
Nhưng không ai biết thực sự có gì ở trong.
const pumpkin: unknown = JSON.parse(`{
"name": "Big Pumpkin",
"candies": ["Chocolate", "Lollipop", "Cookie"],
"spookyLevel": 99,
"keeper": {
"name": "cat",
"friendly": true
},
"secret": "Meow!"
}`);
Một object khá lớn đấy.
Có thể lấy kẹo luôn không?
console.log(pumpkin.candies);
// ❌ 'pumpkin' is of type 'unknown'.
Lại nữa rồi! 😿
Phải kiểm tra bên trong trước khi chạm vào bất cứ thứ gì.
May mà chúng ta có struct!
import {
arrayOf,
isBoolean,
isNumber,
isString,
optionalKey,
struct,
} from "is-kit";
const isPumpkin = struct({
name: isString,
candies: arrayOf(isString),
spookyLevel: isNumber,
keeper: struct({
name: isString,
friendly: isBoolean,
}),
secret: optionalKey(isString),
});
Chúng ta đã ghép nhiều guard nhỏ thành một guard lớn cho bí ngô.
isStringkiểm tra string.isNumberkiểm tra số hữu hạn.arrayOf(isString)kiểm tra mảng string.structkiểm tra cấu trúc object lồng nhau.optionalKeycho phép thiếu một property.
Giờ mở bí ngô nào!
import { safeParse } from "is-kit";
const result = safeParse(isPumpkin, pumpkin);
if (result.valid) {
console.log(result.value.candies);
// ['Chocolate', 'Lollipop', 'Cookie']
console.log(result.value.keeper.name);
// cat 😸
}
TÌM THẤY KẸO RỒI!!! 🍬🎉
Khi validation thành công, result.value có type được suy luận từ các guard.
Còn khi validation thất bại?
const cursedPumpkin = {
name: "Cursed Pumpkin",
candies: "100 candies",
spookyLevel: 99,
keeper: {
name: "cat",
friendly: true,
},
};
const result = safeParse(isPumpkin, cursedPumpkin);
console.log(result);
// { valid: false }
candies là string chứ không phải array!
Guard đã từ chối quả bí ngô bị nguyền rủa.
Đây là điểm quan trọng: safeParse không tự động sửa hay chuyển đổi giá trị không hợp lệ.
Nó chỉ cho biết giá trị có vượt qua guard hay không.
Ngoài ra, struct mặc định cho phép property bổ sung. Nếu muốn từ chối những own enumerable string keys dư thừa, có thể dùng { exact: true }.
Đơn giản vậy thôi!
🍬 Chương 3: Trick or Type Guard!
Ding dong! 🔔
CHO KẸO HAY BỊ GHẸO!!! 😸
Ai đó đưa cho chúng ta một món quà bí ẩn.
function receiveGift(): unknown {
return Math.random() > 0.5 ? "Chocolate" : { spiders: 100 };
}
const gift: unknown = receiveGift();
Khoan đã.
Là kẹo? Hay một hộp đầy nhện?! 🕷️😱
Phải kiểm tra thôi!
Lần này chúng ta muốn chấp nhận string hoặc number.
Hãy kết hợp guard bằng or!
import { isNumber, isString, or } from "is-kit";
const isTreat = or(isString, isNumber);
function trickOrTreat(gift: unknown) {
if (isTreat(gift)) {
// gift: string | number
return `🍬 Treat: ${gift}`;
}
return "👻 Trick!";
}
Thử nhé!
trickOrTreat("Chocolate");
// 🍬 Treat: Chocolate
trickOrTreat(100);
// 🍬 Treat: 100
trickOrTreat({ spiders: 100 });
// 👻 Trick!
YAY!!! 🍬
Hôm nay không phải đụng tới nhện!
Đây là một trong những điều mình thích nhất ở is-kit.
Chúng ta không cần viết một validator khổng lồ.
Chỉ cần tạo những guard nhỏ, kết hợp và tái sử dụng ở bất cứ đâu cần thiết.
Guard nhỏ. Kết hợp đơn giản. TypeScript narrowing hữu ích.
Đó chính là ý tưởng!
Nhưng đợi đã... thứ gì đó đang đến.
Một thứ CỰC KỲ ĐÁNG SỢ.
🧛 Trùm cuối: Ma cà rồng tên any
Đột nhiên đèn tắt phụt.
Cánh cửa mở ra.
Một bóng người bí ẩn xuất hiện trong bóng tối.
Đó là... BÁ TƯỚC ANY!!! 🧛💀
Ma cà rồng đáng sợ nhất dự án TypeScript! Hắn có một quyền năng cực kỳ nguy hiểm.
Hắn khiến TypeScript ngừng kiểm tra các thao tác của chúng ta!
const visitor: unknown = JSON.parse('{"name":666}');
Một vị khách bí ẩn vừa đến.
Thông thường, TypeScript bảo vệ chúng ta khỏi việc truy cập thuộc tính trước khi xác minh chúng.
Nhưng Bá tước Any có một mánh khóe.
LỜI NGUYỀN as any! 🧛
const vampire = visitor as any;
// The vampire ignores type safety!
console.log(vampire.name.toUpperCase());
Không có TypeScript error!
Khoan, GÌ VẬY?! 🙀
Vậy code đã an toàn sao?
Ở runtime:
TypeError: vampire.name.toUpperCase is not a function
💀 GAME OVER 💀
Bá tước Any không biến number thành string.
Hắn chỉ khiến TypeScript ngừng kiểm tra!
Đó là lý do any nguy hiểm: nó cho phép thao tác mà không đòi hỏi bằng chứng thao tác đó có thể chạy đúng.
Còn unknown thì khác.
const visitor: unknown = JSON.parse('{"name":666}');
console.log(visitor.name.toUpperCase());
// ❌ TypeScript stops us!
TypeScript không cho tiếp tục trước khi kiểm tra giá trị.
Vậy làm sao đánh bại Bá tước Any?
Tất nhiên là bằng Type Guard! 😸
import { isString, struct } from "is-kit";
const isFriendlyGuest = struct({
name: isString,
});
if (isFriendlyGuest(visitor)) {
console.log(visitor.name.toUpperCase());
} else {
console.log("🧄 Not tonight, Count Any!");
}
Và thế là...
MA CÀ RỒNG ĐÃ BỊ ĐÁNH BẠI! 🧄🎉
Ít nhất, giá trị bị nguyền rủa cụ thể này đã bị từ chối.
Type Guard kiểm tra giá trị runtime thực tế, còn type assertion chỉ thay đổi điều TypeScript tin về giá trị đó.
Một chi tiết quan trọng: cả unknown lẫn any đều không thay đổi giá trị ở runtime.
Chúng là các type trong TypeScript.
Sự khác biệt nằm ở những thao tác compiler cho phép trước khi xác minh giá trị.
Vì thế, unknown thường an toàn hơn đối với dữ liệu không đáng tin cậy.
😸 Lời kết: Chúng ta sống sót qua Halloween!
Đúng là một đêm đáng nhớ!
Chúng ta đã gặp xác sống bí ẩn, mở bí ngô khổng lồ, nhặt kẹo và đánh bại Bá tước Any!
Vậy chúng ta học được gì?
| Quái vật | Bài học TypeScript |
|---|---|
| 🧟 Xác sống bí ẩn | unknown ngăn thao tác chưa được kiểm tra |
| 🎃 Bí ngô khổng lồ | struct xác minh cấu trúc object |
| 🍬 Trick or Treat | or kết hợp các type guard tái sử dụng được |
| 🧛 Bá tước Any | any bỏ qua type checking thông thường |
Tất nhiên, any không phải lúc nào cũng xấu.
Đôi khi nó hữu ích khi làm việc với legacy code hoặc tích hợp phức tạp.
Nhưng với API response, JSON đã parse và những giá trị không đáng tin cậy khác, giữ chúng là unknown đến khi validation thường là cách an toàn hơn nhiều.
Và is-kit giúp việc kết hợp và tái sử dụng các phép kiểm tra trở nên dễ dàng!
Muốn tìm hiểu thêm? Xem hướng dẫn chính thức 👇
📖 Cách validate unknown trong TypeScript mà không cần schema library
Và nhớ nhé:
Quái vật thực sự không phải unknown, mà là any chưa được kiểm tra. 🧛
Chúc mừng Halloween!!! 🎃👻
Nếu thích cuộc phiêu lưu TypeScript nhỏ này, hãy khám phá is-kit, bộ công cụ type guard nhẹ, zero-dependency, có thể kết hợp và tái sử dụng, đồng thời giữ narrowing tự nhiên.
Nếu thích project, một ⭐ trên GitHub sẽ khiến bé mèo này rất vui! 😸
Cảm ơn bạn đã đọc! 🤲
All Rights Reserved