CHAPTER 04 / TYPE SAFETY
TypeScript:
給 JS 穿盔甲
JS 寫起來自由像穿短褲,但客戶網站上線後 bug 一堆也是因為這個自由。 TypeScript(簡稱 TS)就是給 JS 穿盔甲:型別檢查、自動補全、bug 在寫的時候就攔下來。 現在大公司新專案 90% 是 TS,會 TS 你的薪水多三成。
- 看懂 React/Vue/Next 等任何 TS 專案
- 會給變數、函式、物件加型別
- 會用 interface、type、泛型寫可重用的型別
- VS Code 自動補全暢通無阻
- 能把舊 JS 專案逐步遷到 TS
LESSON 4.1
為什麼要學 TypeScript?
看這個 JS 函式:
function add(a, b) {
return a + b;
}
add(1, 2); // 3 ✓
add('1', 2); // '12' 😱 字串拼接
add(undefined, 2);// NaN 💥 線上爆炸
JS 不會擋你。客戶用了,壞了,你才知道。TS 在寫的當下就告訴你錯:
function add(a: number, b: number): number {
return a + b;
}
add('1', 2); // ❌ 編輯器立刻紅線:'1' 不是 number
JS 像在地下道騎機車不戴安全帽,騎得很快很爽,跌倒就頭破血流。TS 是強制戴安全帽,剛開始覺得煩,跌倒時你會感謝它。
怎麼開始
# 全域安裝
npm install -g typescript
# 編譯
tsc app.ts # 產生 app.js
tsc --watch # 自動編譯
# 直接執行 .ts(推薦)
npm install -g tsx
tsx app.ts
實際工作中你不會手動 tsc,會用 Vite、Next.js 等框架幫你處理。
LESSON 4.2
基本型別
let name: string = '小明';
let age: number = 15;
let isStudent: boolean = true;
let hobbies: string[] = ['寫程式', '打球'];
let scores: number[] = [85, 92, 78];
// 元組(固定長度、固定型別)
let point: [number, number] = [10, 20];
// 任何型別(用了等於沒用 TS)
let wild: any = '隨便';
// 不知道是什麼,但要用之前要先檢查
let input: unknown = getUserInput();
if (typeof input === 'string') {
input.toUpperCase(); // 這裡 TS 知道是 string
}
不要濫用 any。它讓 TS 完全失效。寫 any 不如寫 JS。實在不知道型別用 unknown,至少強迫你檢查。
聯合型別 |
let id: string | number;
id = 'abc'; // ✓
id = 123; // ✓
id = true; // ❌
// 字面量型別(只能是這幾個值)
let theme: 'light' | 'dark' | 'auto';
theme = 'red'; // ❌
null / undefined
let nick: string | null = null;
let phone?: string; // 等同 string | undefined
LESSON 4.3
物件與 interface
interface User {
name: string;
age: number;
email?: string; // 選填
readonly id: number; // 唯讀
}
const u: User = {
id: 1,
name: '小明',
age: 15
};
u.id = 2; // ❌ readonly
interface vs type
// interface(適合物件、可重複擴充)
interface User { name: string; }
interface User { age: number; } // 自動合併
// type(適合聯合、複雜型別)
type ID = string | number;
type Status = 'pending' | 'done';
type Admin = User & { role: 'admin' }; // 交叉
規則:物件結構用 interface,其他用 type。React component 的 props 多數團隊用 type。
函式型別
function greet(name: string): string {
return `嗨 ${name}`;
}
// 箭頭函式
const add = (a: number, b: number): number => a + b;
// 函式型別別名
type Handler = (e: Event) => void;
const onClick: Handler = (e) => console.log(e);
LESSON 4.4
泛型 Generics
泛型 = 型別的變數。當你不想固定一種型別時用。
// 不用泛型
function firstString(arr: string[]): string { return arr[0]; }
function firstNumber(arr: number[]): number { return arr[0]; }
// 寫不完...
// 用泛型
function first<T>(arr: T[]): T {
return arr[0];
}
first([1, 2, 3]); // 自動推斷成 number
first(['a', 'b']); // 自動推斷成 string
常見實例:API 回應
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
async function getUser(): Promise<ApiResponse<User>> {
const r = await fetch('/api/me');
return r.json();
}
LESSON 4.5
實用內建工具型別
interface User {
id: number;
name: string;
email: string;
}
// 全部變選填
type PartialUser = Partial<User>;
// 全部變必填
type RequiredUser = Required<User>;
// 全部變 readonly
type FrozenUser = Readonly<User>;
// 只挑某些欄位
type UserPreview = Pick<User, 'id' | 'name'>;
// 排除某些欄位
type UserNoEmail = Omit<User, 'email'>;
// 從 union 排除
type Status = 'a' | 'b' | 'c';
type NoC = Exclude<Status, 'c'>; // 'a' | 'b'
LESSON 4.6
tsconfig.json 必開選項
{
"compilerOptions": {
"target": "ES2022",
"strict": true, // 開!全部型別檢查
"noUnusedLocals": true,
"noImplicitAny": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler"
}
}
"strict": true 必開。新手常為了「先讓專案動」而關掉,最後 bug 跟 JS 一樣多,等於白裝 TS。
練習:型別化你的 To-Do List
把第 3 章的 To-Do List 從 JS 改成 TS:
- 定義
Todointerface(id、text、done、createdAt) - 所有函式參數加型別
- localStorage 讀回來時做型別檢查
- 定義
Filter = 'all' | 'active' | 'done' - 用泛型寫一個
useLocalStorage<T>函式
常見卡關 FAQ
// TS 學員最常問的問題
VS Code 一直紅線怎麼辦?
紅線是 TS 在救你!讀錯誤訊息:通常是「型別 A 不能指派給 B」,看 A 跟 B 差在哪。多數情況加個
? 或處理 null 就好。第三方套件沒有型別?
先試
npm i -D @types/套件名。沒有的話自己寫個 declare module '套件名'; 在 types.d.ts,當 any 用。interface 跟 type 我到底用哪個?
能用 interface 就用 interface(錯誤訊息較好讀、可宣告合併);要 union、tuple、複雜計算才用 type。團隊有規定就跟團隊。
as 強制轉型可以嗎?
能不用就不用。
as 是「我比 TS 聰明」的承諾,承諾錯了上線就爆。比較常用的合理場景:document.querySelector('input') as HTMLInputElement。JS 專案要全改 TS 嗎?
不必。TS 支援
allowJs: true 漸進式遷移。先把新檔寫 TS,舊 JS 慢慢搬。一次全改往往會放棄。