Hoi hoi! 👋

我是 @nyaomaru,一位很怕恐怖事物的前端工程師 🙀

萬聖節快樂! 🎃👻

今晚,我們要探索一個鬧鬼的 TypeScript 專案。

這裡有殭屍 🧟、鬼魂 👻、神祕南瓜 🎃,還有一隻可怕的吸血鬼 🧛 等著我們。

而且它們都和 TypeScript 型別有關!

我們的任務是?

在不失去型別安全的情況下撐過這一晚! 😸

出發吧!

halloween is-kit


🧟 第 1 章:未知的活死人

這是一個黑暗又陰森的夜晚。

突然,有東西從墳墓裡爬出來了! 🧟

const creature: unknown = JSON.parse('{"message":"BRAAAINS!"}');

喔!是一個 unknown 生物!

來看看它想幹嘛。

console.log(creature.message);
// ❌ 'creature' 的型別是 'unknown'。

什麼?!🙀

TypeScript 不讓我們這樣做!

但是為什麼?

因為我們其實不知道 creature 到底是什麼。

它可能是殭屍。
它可能是鬼魂。
它甚至可能是一隻可愛的小貓。😸

unknown 表示我們還沒有驗證這個值。

跟 any 不同,TypeScript 不會讓我們對 unknown 值任意存取屬性。

而這其實是好事!

我們來試個更簡單的例子。

import { isString } from "is-kit";

const voice: unknown = JSON.parse('"BRAAAINS!"');

if (isString(voice)) {
  console.log(voice.toUpperCase());
  // BRAAAINS!
}

YA!🎉

我們撐過第一次遭遇了!

當然,這裡我們也可以直接用 typeof voice === 'string'。

如果只做單一檢查,這完全沒問題!

但當怪物變得更大、更複雜時,又會怎樣呢?


🎃 第 2 章:巨大的未知南瓜

看!

一顆超巨大的南瓜! 🎃

而且看起來裡面裝滿了糖果。

但沒有人知道裡面到底有什麼。

const pumpkin: unknown = JSON.parse(`{
  "name": "Big Pumpkin",
  "candies": ["Chocolate", "Lollipop", "Cookie"],
  "spookyLevel": 99,
  "keeper": {
    "name": "cat",
    "friendly": true
  },
  "secret": "Meow!"
}`);

這是一個相當大的物件。

我們可以直接拿糖果嗎?

console.log(pumpkin.candies);
// ❌ 'pumpkin' 的型別是 'unknown'。

又來了!😿

我們必須先檢查裡面有什麼,才能碰它。

幸好,我們有 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),
});

我們把好幾個小型 guard 組合成一個大型南瓜 guard。

  • isString 檢查字串。
  • isNumber 檢查有限數字。
  • arrayOf(isString) 檢查字串陣列。
  • struct 檢查巢狀物件結構。
  • optionalKey 允許某個屬性不存在。

現在,來打開南瓜吧!

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 😸
}

我們找到糖果啦!!!🍬🎉

當驗證成功時,result.value 會根據我們的 guards 推斷出型別。

那如果驗證失敗呢?

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 }

糖果是字串,不是陣列!

我們的 guard 擋下了被詛咒的南瓜。

而這裡很重要的一點是:

safeParse 不會神奇地修正或轉換不合法的值。

它只是告訴我們這個值有沒有通過 guard。

另外,struct 預設允許額外屬性存在。如果你需要拒絕額外的自有可列舉字串鍵,可以使用 { exact: true }。

簡單又好用!


🍬 第 3 章:萬聖節搗蛋還是型別守衛!

叮咚!🔔

不給糖就搗蛋!!!😸

有人遞給我們一份神祕禮物。

function receiveGift(): unknown {
  return Math.random() > 0.5 ? "Chocolate" : { spiders: 100 };
}

const gift: unknown = receiveGift();

等等。

這是糖果嗎?
還是一盒蜘蛛?!🕷️😱

我們需要一種方法來檢查它!

而這次,我們想接受字串或數字其中之一。

讓我們用 or 把 guards 組合起來!

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!";
}

現在來試試看!

trickOrTreat("Chocolate");
// 🍬 Treat: Chocolate

trickOrTreat(100);
// 🍬 Treat: 100

trickOrTreat({ spiders: 100 });
// 👻 Trick!

YA!!!🍬

今天不用面對蜘蛛了!

這正是我很喜歡 is-kit 的地方之一。

我們不需要寫一個巨大的驗證器。

我們可以建立小型 guards,把它們組合起來,然後在需要的地方重複使用。

小型 guards、簡單組合、實用的 TypeScript 縮窄。

概念就是這樣!

但是等等,
有什麼東西正在靠近。
而且是非常可怕的東西。


🧛 最終魔王:名為 any 的吸血鬼

突然,燈熄了。

門開了。

黑暗中出現了一個神祕身影。

那是……ANY 伯爵!!!🧛💀

我們 TypeScript 專案裡最可怕的吸血鬼!而且他有一個非常危險的能力。

他能讓 TypeScript 停止檢查我們的操作!

const visitor: unknown = JSON.parse('{"name":666}');

一位神祕訪客來了。

通常,TypeScript 會在我們驗證之前保護我們,避免直接存取屬性。

但 Any 伯爵有一招。

as any 的詛咒!🧛

const vampire = visitor as any;

// 這個吸血鬼會無視型別安全!
console.log(vampire.name.toUpperCase());

沒有 TypeScript 錯誤!

等等,什麼?!🙀

那是不是代表這段程式碼是安全的?

在執行時

TypeError: vampire.name.toUpperCase is not a function

💀 遊戲結束 💀

Any 伯爵並沒有把數字變成字串。

他只是讓 TypeScript 不要再檢查它了!

這就是 any 危險的地方。

它允許我們在沒有證明操作可行的情況下直接執行。

但 unknown 的行為不同。

const visitor: unknown = JSON.parse('{"name":666}');

console.log(visitor.name.toUpperCase());
// ❌ TypeScript 擋下我們!

TypeScript 會拒絕繼續,直到我們檢查這個值為止。

那我們要怎麼打敗 Any 伯爵?

當然是靠 Type Guard!😸

import { isString, struct } from "is-kit";

const isFriendlyGuest = struct({
  name: isString,
});

if (isFriendlyGuest(visitor)) {
  console.log(visitor.name.toUpperCase());
} else {
  console.log("🧄 今天不行,Any 伯爵!");
}

就這樣

吸血鬼被擊敗了! 🧄🎉

好吧,至少這個被詛咒的值已經被拒絕了。

Type Guard 會檢查實際的執行時值,而型別斷言只是在改變 TypeScript 對它的認知。

有一個重要細節,unknown 和 any 都不會改變執行時的值。

它們都是 TypeScript 的型別。

差別在於,在驗證值之前,編譯器允許我們做什麼。

所以,對於不可信任的資料,unknown 通常是更安全的選擇。


😸 尾聲:我們撐過萬聖節了!

這真是漫長的一夜!

我們遇到了未知的活死人、打開了巨大的南瓜、收集了一些糖果,還打敗了 Any 伯爵!

所以,我們學到了什麼?

怪物 TypeScript 課題
🧟 未知的活死人 unknown 會阻止未經檢查的操作
🎃 巨大南瓜 struct 用來驗證物件結構
🍬 不給糖就搗蛋 or 可以組合可重用的型別守衛
🧛 Any 伯爵 any 會繞過一般的型別檢查

當然,any 不一定總是邪惡的。

有時候,當你面對舊有程式碼或難以整合的系統時,它會很有用。

但在處理 API 回應、JSON 解析結果,以及其他不可信任的值時,在驗證之前先保持它們為 unknown,通常是更安全的做法。

而 is-kit 讓這些檢查更容易組合與重複使用!

如果你想了解更多,請看看官方指南 👇

📖 如何在不使用 Schema 函式庫的情況下於 TypeScript 中驗證 unknown

還有,記得

真正的怪物不是 unknown,而是未經檢查的 any。 🧛

萬聖節快樂!!!🎃👻

如果你喜歡這段小小的 TypeScript 冒險,來看看 is-kit 吧!

https://github.com/nyaomaru/is-kit

如果你喜歡這個專案,幫它點個 ⭐ 會讓這隻小貓非常開心!😸

謝謝閱讀!🤲


原文出處:https://dev.to/nyaomaru/trick-or-type-guard-surviving-unknown-with-is-kit-4oc2


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
boluoji
📝20   💬3   ❤️6
401
🥈
站長阿川
📝5   💬2   ❤️1
214
🥉
mike
1
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登