iT邦幫忙

2025 iThome 鐵人賽

DAY 30
0
Modern Web

我與型別的 30 天約定:TypeScript 入坑實錄系列 第 30

Day 30|總結與下一步:把 TypeScript 變成你的團隊超能力

  • 分享至 

  • xImage
  •  

今天不只回顧 30 天的內容,我會把「設計心法 → 專案落地檢查清單 → 升級路線」一次給齊。

你可以拿這篇當 團隊 Onboarding 指南,也能當自己做專案的 Checklist。走起。


一、30 天重點回顧(超濃縮)

  • 型別基礎(Day 1–10):基本型別、物件/陣列/Enum、函式型別、泛型(Generics)。
  • 中級心法(Day 11–14):泛型進階、型別守衛(Type Guards)、Utility Types。
  • 實戰串接(Day 15–20):Node + TS、dotenv + zod、路由驗證、Prisma ORM、React + TS、錯誤流(Result + ApiError)。
  • 高階型別(Day 21–28):映射型別、條件型別、Template Literal Types、型別驅動的表單/事件系統。
  • 型別驅動 API Client(Day 29):單一 Schema → 產生安全路徑/參數/回傳的 Client,接 zod + Result。

一句話總結:

用「單一真相來源」+ 「型別工具鏈」,讓前後端協作、API、表單、事件、錯誤流,都能在編譯期先踩煞車。


二、TS 設計心法(真的能救命)

  1. 資料模型單一真相(SSOT)

    型別集中於 src/types/ 或 shared 套件;UI / API / DB 都由它衍生,拒絕重複抄一遍。

  2. 窄化優先,any 最後招

    typeofinstanceofin、自訂守衛、Discriminated Union 把不確定消掉;

    any 只在邊界(第三方)使用,且立刻轉回安全型別。

  3. 泛型要「必要且有關聯」

    T, K extends keyof T, T[K]:參數之間要能互相約束。

    過度抽象 = 可讀性地獄。

  4. 錯誤是型別,不是例外

    Result<T, E> + ApiError 搭配 zod,讓成功/失敗「各有自己的型別」,呼叫端不必 everywhere try/catch。

  5. Runtime 驗證永遠要配型別

    環境變數、API 入參/回傳、第三方資料 → zod/valibot 驗證,z.infer 同步 TS 型別。

  6. 範圍思維:把「字串」變成型別

    Template Literal Types + Union:事件名、API 路徑、CSS Class 等「字串規則」→ 型別化

  7. 永遠留一個 never 兜底

    assertNever(x: never) 保證分支全面;新增成員時,編譯器會叫醒你。


三、專案落地檢查清單(Team-ready)

1) tsconfig.json(嚴格但好用)

json
CopyEdit
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "exactOptionalPropertyTypes": true,
    "noUncheckedIndexedAccess": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "useUnknownInCatchVariables": true,
    "importsNotUsedAsValues": "error"
  }
}

重點:exactOptionalPropertyTypes、noUncheckedIndexedAccess 讓你少踩很多邊角雷。

2) Lint & Format

  • ESLint:@typescript-eslint/parser@typescript-eslint/eslint-plugin
  • 推薦規則:
    • @typescript-eslint/consistent-type-imports
    • @typescript-eslint/explicit-function-return-type(公共 API 上開)
    • @typescript-eslint/no-explicit-any(允許極少數例外註解)
  • Prettier:統一風格、避免無聊 diff。

3) 型別目錄 & 共用策略

bash
CopyEdit
src/
  types/          # Domain models、API types、Result、Error
  utils/          # 型別工具、守衛
  features/...
shared/           # (mono-repo) 前後端共用的 schema/type

  • API:單一 Schema(Day 29)→ Client / Server 共用
  • Env:src/env.ts + zod(Day 16)

4) API & 驗證

  • 入站(Server):zod.parse 擋垃圾資料
  • 出站(Client):zod.parse 保證形狀 + Result 介面化錯誤
  • 路由層validate(schema) middleware(Day 17)

5) DB 層(Prisma)

  • 單例 PrismaClient
  • select 精準挑欄位;include 慎用
  • 交易 $transaction 合批行為
  • 錯誤代碼(P2002)→ 對應 ApiError.code

6) 前端(React + TS)

  • Props:typeinterface,必要時用 ComponentProps<"button"> 合併原生
  • 狀態:useState<T | null> 明確 null
  • 事件:React.MouseEvent<HTMLButtonElement>
  • 表單:zod + react-hook-formzodResolver
  • 資料讀取:@tanstack/react-query + 泛型(useQuery<User[]>

7) 測試

  • 邏輯:Vitest / Jest
  • 型別:tsdexpectTypeOf(Vitest 4)
  • 行為:Playwright / Cypress(端到端)

8) CI

  • tsc -p tsconfig.json --noEmit 型別檢查
  • eslint .
  • vitest run
  • 推薦加 pnpm dedupe / npm dedupe,減少依賴地雷。

四、從 JS 漸進式遷移 TS(實戰藍圖)

  1. 加 tsconfig、允許 allowJs: true(首期)
  2. 先型別化「邊界」:環境變數、API 入出參、第三方回傳
  3. 將工具函式、Model、Service 層改為 TS
  4. 打開 strictnoImplicitAny掃紅線
  5. 模組化:共用 types/、抽出 ResultApiError
  6. 關閉 allowJs,全量 TS

每一步都可獨立合併、回滾風險小。


五、常見坑 & 反模式(附解法)

  • 「值」當「型別」const path = "/api"; type T = \prefix-${path} ❌ → 必須是字面型別:type Path = "/api";

  • 什麼都 any:短期舒服、後期火葬場

    → 用 unknown + 守衛;邊界 zod

  • 過度泛型:讀不懂 → 重構成具體型別 + 少量泛型

  • Enum 亂用:多半用 Literal Union"A" | "B")較輕

  • as 成癮as 是逃生門,不是正門

    → 先想「如何讓編譯器推論到」,必要時再 as const/satisfies


六、進階技巧(你會愛)

1) Branded / Opaque Types(近似名目型別)

ts
CopyEdit
type Brand<K, T> = K & { __brand: T };
type UserId = Brand<string, "UserId">;

function getUser(id: UserId) {}
// getUser("abc") ❌ 不能亂傳
const id = "abc" as UserId;
getUser(id); // ✅

2) satisfies:讓值符合型別、又不丟失更精準的推論

ts
CopyEdit
const routes = {
  users: "/api/users",
  posts: "/api/posts",
} as const satisfies Record<string, `/${string}`>;

3) as const:把值「縮窄成字面量」

ts
CopyEdit
const ROLES = ["admin", "user", "guest"] as const;
type Role = typeof ROLES[number]; // "admin" | "user" | "guest"

4) 積木式型別組合

  • Pick / Omit / Partial / Required / Readonly
  • Record<K, T> 生映射表
  • ReturnType<T>Parameters<T> 從函式抽型別

5) never 兜底

ts
CopyEdit
function assertNever(x: never): never {
  throw new Error("Unhandled: " + x);
}


七、工具箱清單(收藏)

  • 型別工具type-festts-toolbeltzodvalibotio-ts
  • DXts-reset(修補內建型別)、tsup/tsc -wtsx(跑 TS)
  • 資料層prismadrizzle
  • 前端@tanstack/react-queryreact-hook-form + @hookform/resolvers/zod
  • APItRPC(端到端型別)、OpenAPI + 代碼產生
  • 測試vitesttsdexpect-type、E2E:playwright

八、升級路線(學完這 30 天,接下來學啥)

  1. 型別程式設計(Type-level programming)
    • 條件 + 映射 + 模板字面,寫出複雜型別運算
  2. tRPC / OpenAPI → 自動 Client
    • 讓後端 schema 自動長出前端型別與函式
  3. 型別驅動 UI
    • 由資料型別自動生成表單、table 欄、路由、i18n key(Day 27 開始進化)
  4. 大型專案治理
    • Monorepo、shared types、CI 型別守門、PR Bot 自動跑 tsc --noEmit

九、Capstone 建議(做完你就真的熟了)

題目:做一個「型別驅動的部落格 CMS

  • 後端:Node + Express + Prisma(User/Post/Tag)
  • 型別:shared types/(User/Post DTO、ApiError、Result)
  • 驗證:zod(env + 路由 + 回傳)
  • 前端:React + TS;表單(Day 27)、事件(Day 28)
  • API:Day 29 Client;React Query 串接
  • 測試:Vitest(邏輯)、tsd(型別)
  • CI:tsc --noEmiteslintvitest run

交付物:README 截圖、/docs/ 放型別設計圖(自豪地展示你的 SSOT)。


十、Ending:你已經會了,真的

30 天走到這裡,你不只「會用 TS」,你已經具備把 TS 落地到專案的能力。

接下來的功課只有一個:一直用它,讓型別變成你每天省時間的武器


上一篇
Day 29|型別驅動的 API 客戶端生成器:路徑、參數、回傳,一次到位
系列文
我與型別的 30 天約定:TypeScript 入坑實錄30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言