Zod:TypeScript 優先的模式驗證與靜態類型推斷

作者: Calpa Liu
字數:1585
出版:2025年4月1日
分類: TypeScript 開發工具 前端開發 資料驗證

Zod 是一個專為 TypeScript 設計的強大模式驗證庫,它能夠無縫橋接編譯時類型系統與執行時環境,為開發者提供全面的類型安全保障。在現代 Web 應用程式開發中,尤其是處理使用者輸入、API 資料或 AI 整合時,Zod 已成為許多開發團隊不可或缺的工具。本文將深入探討 Zod 的核心特性以及使用它的主要優勢。

Zod 的核心概念

Zod 的設計理念是消除重複的類型宣告。在傳統的 TypeScript 開發中,我們經常需要先定義一個類型,然後再為該資料編寫驗證邏輯,這導致了重複的程式碼和潛在的不一致性。Zod 通過讓你定義一次驗證器,然後自動推斷出相應的 TypeScript 類型,解決了這個問題。

基本使用示例

import { z } from "zod";

// 創建一個基本的字串模式
const stringSchema = z.string();

// 解析和驗證
stringSchema.parse("hello"); // 成功返回 "hello"
try {
  stringSchema.parse(12); // 抛出 ZodError
} catch (error) {
  console.log((error as z.ZodError).errors);
  /* 
    [
        {
            code: 'invalid_type',
            expected: 'string',
            received: 'number',
            path: [],
            message: 'Expected string, received number'
        }
    ]
  */
}

// 安全解析 (不抛出錯誤)
const result = stringSchema.safeParse("hello");
if (result.success) {
  console.log(result.data); // "hello"
} else {
  console.log((result.error as z.ZodError).errors); // ZodError 實例
}

// 建立對象模式
const User = z.object({
  username: z.string(),
  email: z.string().email(),
  age: z.number().min(18),
});

// 自動推斷類型
type User = z.infer<typeof User>;
// 等同於:{ username: string; email: string; age: number }

Zod 的主要優勢

1. 消除類型重複

Zod 最大的優勢之一是消除了在 TypeScript 中常見的類型重複問題。通過 z.infer 可以直接從 Zod 模式推斷出 TypeScript 類型,讓你的類型定義和驗證邏輯保持同步。

import { z } from "zod";

const UserSchema = z.object({
    id: z.string().uuid(),
    name: z.string().min(2),
    email: z.string().email(),
    role: z.enum(["admin", "user", "guest"]),
    metadata: z.record(z.string()),
    lastLogin: z.date().optional(),
  });
  
  // 自動推斷類型
type User = z.infer<typeof UserSchema>;

const user: User = {
    id: "8dd7d69c-ab17-468c-80b5-c71658aa6b07",
    name: "John Doe",
    email: "john.doe@example.com",    
    role: "admin",
    metadata: {
        age: "30"
    },
    lastLogin: new Date("2023-03-15T12:34:56.789Z")
}

const result = UserSchema.parse(user);
console.log(result);

/*
{
  id: '8dd7d69c-ab17-468c-80b5-c71658aa6b07',
  name: 'John Doe',
  email: 'john.doe@example.com',
  role: 'admin',
  metadata: { age: '30' },
  lastLogin: 2023-03-15T12:34:56.789Z
}
*/

2. 執行時類型安全

TypeScript 僅提供編譯時的類型檢查,一旦編譯完成,類型安全就消失了。Zod 彌補了這一缺口,通過在執行時驗證資料結構,確保輸入確實符合預期的類型和格式。

// 表單提交處理
function handleSubmit(formData: unknown) {
  const validatedData = UserSchema.parse(formData);
  // 此處 validatedData 已通過驗證且類型安全
  saveToDatabase(validatedData);
}

3. 零依賴與輕量級

Zod 完全沒有依賴其他套件,這使其成為一個非常輕量的解決方案。壓縮後僅約 8kb,遠小於許多同類庫。這對於前端應用尤為重要,因為最小化套件大小可以改善載入時間和使用者體驗。

4. 豐富的驗證功能

Zod 提供了豐富的內建驗證規則和方法,可以處理從簡單原始類型到複雜巢狀對象的各種資料結構。

import { z } from "zod";

// 字串驗證
const username = z.string()
  .min(3, "用戶名至少需要 3 個字符")
  .max(20, "用戶名不能超過 20 個字符");

// 數字驗證
const price = z.number()
  .positive("價格必須為正數")
  .min(0.01, "價格最少為 0.01");

// 複雜對象驗證
const Product = z.object({
  id: z.string().uuid(),
  name: z.string().min(3),
  price: z.number().positive(),
  tags: z.array(z.string()),
  stock: z.number().int().nonnegative(),
});

const user = username.parse("calpa");
console.log(user);

const priceValue = price.parse(10.99);
console.log(priceValue);

const product = Product.parse({
  id: "8dd7d69c-ab17-468c-80b5-c71658aa6b07",
  name: "Product A",
  price: 10.99,
  tags: ["tag1", "tag2"],
  stock: 10,
});

console.log(product);

你可以看到輸出結果,calpa10.99product 都已經通過了驗證。

calpa
10.99
{
  id: '8dd7d69c-ab17-468c-80b5-c71658aa6b07',
  name: 'Product A',
  price: 10.99,
  tags: [ 'tag1', 'tag2' ],
  stock: 10
}

5. 自定義驗證邏輯

通過 .refine().superRefine() 方法,Zod 允許你添加自定義的驗證邏輯,這在處理複雜業務規則時特別有用。

const passwordForm = z
  .object({
    password: z.string().min(8),
    confirmPassword: z.string(),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: "密碼不匹配",
    path: ["confirmPassword"], // 指定錯誤路徑
  });

6. 函數驗證能力

Zod 獨特的功能之一是能夠驗證函數的輸入和輸出。這是其他許多驗證庫沒有的特性。

const myFunction = z
  .function()
  .args(z.string(), z.number())
  .returns(z.boolean())
  .implement((str, num) => {
    // TypeScript 知道 str 是字串,num 是數字
    return str.length > num;
  });

// 使用函數
myFunction("test", 3); // 返回 true
myFunction(123, "test"); // 類型錯誤

7. 與主流框架的良好整合

Zod 與 React、Next.js、tRPC 等現代框架有很好的整合。特別是在構建強類型的全端應用程式時,Zod 可以作為客戶端和服務器端之間的橋樑,確保資料一致性。

Zod 在 AI 開發中的應用

隨著 AI 應用程式的普及,Zod 已成為處理 AI 輸出的重要工具。從 OpenAI 到 Vercel 到 Google,各大 AI 平台的文檔中都出現了 Zod,用於確保 AI 生成的資料符合應用程式預期的結構。

// 定義 AI 輸出的結構
const AIResponseSchema = z.object({
  text: z.string(),
  confidence: z.number().min(0).max(1),
  entities: z.array(
    z.object({
      type: z.enum(["person", "organization", "location"]),
      text: z.string(),
      score: z.number(),
    })
  ),
});

// 處理 AI 回應
async function handleAIResponse(rawResponse: unknown) {
  try {
    const validResponse = AIResponseSchema.parse(rawResponse);
    // 安全處理已驗證的資料
    processEntities(validResponse.entities);
  } catch (error) {
    // 處理驗證錯誤
    console.error("AI 回應格式無效", error);
  }
}

Zod vs 其他驗證庫

與其他常見的驗證庫(如 Yup)相比,Zod 有幾個明顯的優勢:

  1. TypeScript 優先:Zod 從設計之初就考慮到 TypeScript 整合,而 Yup 則是後來才添加 TypeScript 支援。

  2. 包大小更小:Zod 約 8KB,而 Yup 約 24KB。

  3. 更好的型別推斷:Zod 在類型推斷方面表現更好,可以捕捉到 Yup 可能忽略的類型錯誤。

  4. 不可變 API:Zod 的方法返回新實例而不是變更原對象,這與現代 JavaScript 的函數式編程理念更為一致。

結論

Zod 透過無縫橋接 TypeScript 的靜態類型系統和執行時驗證,為開發者提供了一個強大的工具,用於確保資料完整性和類型安全。它的輕量級設計、豐富的驗證功能和與 TypeScript 的深度整合,使其成為現代 Web 開發的理想選擇。

無論是處理表單輸入、API 回應,還是 AI 生成的資料,Zod 都能提供一致且可靠的驗證方案,同時消除重複的類型定義,提高開發效率。對於任何重視類型安全和程式碼品質的 TypeScript 項目,Zod 都是一個值得考慮的工具。

通過採用 Zod 進行資料驗證,你不僅可以提高程式碼的健壯性和可維護性,還能在開發過程中提早發現潛在問題,為用戶提供更好的體驗。

感謝您閱讀我的文章。歡迎隨時分享你的想法。
TypeScript 開發工具 前端開發 資料驗證
關於 Calpa

Calpa 擅長使用 TypeScriptReact.jsVue.js 建立 Responsive Website。

他積極參與開源社區,曾在 2019 年的香港開源大會上擔任講者,提供工作經驗和見解。此外,他也在 GitHub 上公開分享個人博客程式碼,已獲得超過 300 顆星星和 60 個分支的支持。

他熱愛學習新技術,並樂意分享經驗。他相信,唯有不斷學習才能跟上快速演變的技術環境。