本篇是 TypeScript 系列的基礎篇;進階見泛型與工具型別(同目錄)。
目錄
- 什麼是 TypeScript?
- TS / JavaScript / Node.js / 框架的關係
- 基本型別
- 型別註記與型別推論
- 物件型別:interface vs type
- Union 與 Intersection
- 字面值型別與 enum
- 函式型別
- 型別收窄(Narrowing)
- any / unknown / never
- 型別斷言
- tsconfig 與 strict 模式
- 最佳實踐
- 常見問題
- 總結
什麼是 TypeScript?
TypeScript(TS) 是 JavaScript 的超集(superset):在 JS 之上加入靜態型別。所有合法的 JS 都是合法的 TS。
關鍵特性:型別只存在於編譯期,編譯成 JS 後型別會被抹除(type erasure),執行時就是純 JS——型別不影響執行行為。
.ts(含型別)── tsc 編譯 ──► .js(型別被抹除)── 在瀏覽器/Node 執行
為什麼用 TypeScript?
- 編譯期抓錯:型別不符在開發時就報錯,不必等執行才炸
- IDE 支援強:自動完成、跳轉、重構更可靠
- 自我文件化:型別本身就是文件,函式參數/回傳一目了然
- 大型專案維護性:重構時型別系統會抓出受影響的地方
代價:要寫型別、要編譯步驟、有學習曲線。小型拋棄式腳本未必划算。
TS / JavaScript / Node.js / 框架的關係
初學最常見的困惑:「Node.js、Next.js 是不是 TypeScript?」——不是。它們是不同層次的東西:
- TypeScript = 語言(怎麼寫):JS + 型別,編譯成 JS 才執行
- Node.js = 執行環境(runtime)(在哪跑):讓 JS 在瀏覽器外執行
- Next.js / React = 框架 / 函式庫(用什麼蓋)
三者不同層、可搭配——Node.js、Next.js、React 都能用 TypeScript 來寫;TS 最後都編譯成 JS 交給 Node 或瀏覽器執行。TypeScript 只負責「語言」這一層。
完整的生態分層(各層有哪些語言/執行環境/框架、怎麼組合)見 JavaScript 生態全景。
基本型別
let s: string = "hello";
let n: number = 42; // 整數浮點都是 number
let b: boolean = true;
let u: undefined = undefined;
let nl: null = null;
// 陣列
let arr: number[] = [1, 2, 3];
let arr2: Array<string> = ["a", "b"]; // 泛型寫法,等價
// 元組(Tuple):固定長度、各位置型別固定
let pair: [string, number] = ["age", 30];
// any:放棄型別檢查(盡量避免)
let whatever: any = 1;
// unknown:未知型別,但用前必須先收窄(比 any 安全)
let value: unknown = getData();
// void:函式無回傳值
function log(msg: string): void { console.log(msg); }
// never:永不回傳(拋錯或無窮迴圈)
function fail(msg: string): never { throw new Error(msg); }
型別註記與型別推論
TypeScript 能自動推論型別,不必每處都標:
let x = 10; // 推論為 number
x = "hi"; // ❌ 錯:不能把 string 指給 number
const name = "Alice"; // 推論為字面值型別 "Alice"(const 更窄)
// 函式回傳值通常可推論,但「參數」一定要標
function add(a: number, b: number) { // 回傳自動推論為 number
return a + b;
}
原則:讓 TS 推論能推的,只在它推不出或想更精確時才手動標(尤其函式參數、公開 API 邊界)。
物件型別:interface vs type
兩種定義物件結構的方式,常被問差異:
// interface
interface User {
id: number;
name: string;
email?: string; // ? = 選填
readonly createdAt: Date; // readonly = 唯讀
}
// type alias
type UserT = {
id: number;
name: string;
};
差異對照
| 面向 | interface |
type |
|---|---|---|
| 物件 / 類別形狀 | ✅ | ✅ |
| union / intersection / 基本型別別名 | ❌ | ✅(type X = A | B) |
| 宣告合併(同名自動合併) | ✅ | ❌ |
| 擴充 | extends |
&(intersection) |
經驗法則:物件/類別的形狀、需要被 extends 或宣告合併 → 用
interface;union、tuple、需要型別運算 → 用type。團隊一致即可,兩者多數情況可互換。
Union 與 Intersection
// Union(或):是其中之一
type Id = string | number;
let id: Id = 123;
id = "abc"; // 都可以
// Intersection(且):同時滿足多個
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged; // 同時要有 name 和 age
const p: Person = { name: "A", age: 30 };
- Union
|:值是「A 或 B」,使用前常需收窄 - Intersection
&:型別是「A 且 B」的合併
字面值型別與 enum
// 字面值型別:限定為特定值
type Direction = "up" | "down" | "left" | "right";
let dir: Direction = "up";
dir = "north"; // ❌ 不在允許值內
// 常用於函式參數,比 enum 輕量
function move(d: "up" | "down") {}
// enum:具名常數集合
enum Status { Active, Inactive, Pending } // 預設 0,1,2
let st: Status = Status.Active;
實務上字面值 union 常比 enum 更受青睞(更輕量、無執行期產物;enum 會編譯出實際物件)。需要具名常數集合時才用 enum。
函式型別
// 參數與回傳型別
function greet(name: string): string {
return `Hi ${name}`;
}
// 選填參數、預設值、其餘參數
function build(a: string, b?: string, c = "x", ...rest: number[]) {}
// 函式型別表達式(用於回呼)
type Handler = (event: string) => void;
const h: Handler = (e) => console.log(e); // e 自動推論為 string
// 箭頭函式
const square = (n: number): number => n * n;
型別收窄(Narrowing)
當值是 union 型別時,TS 會依控制流程自動把型別收窄到更精確的範圍:
function format(x: string | number) {
if (typeof x === "string") {
return x.toUpperCase(); // 這裡 x 已收窄為 string
}
return x.toFixed(2); // 這裡 x 已收窄為 number
}
常用收窄手段
| 手段 | 用途 |
|---|---|
typeof x === "string" |
基本型別 |
x instanceof Foo |
類別實例 |
"prop" in x |
物件是否有某屬性 |
真值判斷 if (x) |
排除 null / undefined / 0 / "" |
| 可辨識聯合(discriminated union) | 用共同的字面值欄位區分 |
可辨識聯合(很實用)
用一個共同的「標籤」欄位區分 union 成員:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2; // 收窄為 circle
case "square": return s.side ** 2; // 收窄為 square
}
}
any / unknown / never
三個容易混淆的特殊型別:
| 型別 | 意義 | 用法 |
|---|---|---|
any |
關閉型別檢查,什麼都能做 | 盡量避免,等於放棄 TS 的保護 |
unknown |
「未知」,但使用前必須先收窄 | 接外部/不可信資料的安全選擇 |
never |
不可能有值(永不發生) | 窮盡檢查、永遠拋錯的函式 |
let a: any = 5;
a.foo.bar(); // 不報錯(危險)
let u: unknown = 5;
u.toFixed(); // ❌ 報錯:要先收窄
if (typeof u === "number") u.toFixed(); // ✅
// never:窮盡檢查(漏掉 case 會編譯期報錯)
function assertNever(x: never): never { throw new Error("unexpected"); }
優先用
unknown取代any:保留型別安全,逼你在使用前先檢查。
型別斷言
告訴編譯器「我比你更確定這個值的型別」:
const el = document.getElementById("app") as HTMLInputElement;
el.value = "x"; // 斷言成 input 才有 .value
// 非空斷言 !:保證不是 null/undefined
const node = document.querySelector(".btn")!;
⚠️ 斷言是繞過檢查,不是轉換——斷錯了執行期照樣出錯。能用收窄就別用斷言;
!尤其要謹慎,它只是叫 TS 閉嘴。
tsconfig 與 strict 模式
tsconfig.json 控制編譯行為,最重要的是 strict:
{
"compilerOptions": {
"strict": true, // 開啟所有嚴格檢查(強烈建議)
"target": "ES2022", // 編譯成哪個 JS 版本
"module": "ESNext",
"noUncheckedIndexedAccess": true // 索引存取也要考慮 undefined
}
}
strict: true 會開啟一系列檢查,最重要的:
strictNullChecks:null/undefined不再能隨意指給其他型別——TS 最有價值的保護之一noImplicitAny:不允許隱含的any
新專案一律開
strict。關掉等於放棄 TS 大半價值。
最佳實踐
- 開
strict,尤其strictNullChecks - 避免
any,優先unknown接不可信資料 - 讓 TS 推論能推的,只在邊界(函式參數、公開 API)手動標型別
- union + 可辨識聯合 + 收窄,少用型別斷言
- 字面值 union 優先於 enum(更輕量)
- 斷言與
!當最後手段,它們繞過檢查而非保證安全 - interface / type 選一套慣例並一致
常見問題
問題 1:interface 和 type 該用哪個?
物件/類別形狀、需要 extends 或宣告合併 → interface;union、tuple、型別運算 → type。多數情況可互換,團隊一致最重要。
問題 2:any 和 unknown 差在哪?
any 完全關閉檢查(危險);unknown 是「未知但安全」——使用前必須先收窄型別。接外部資料優先用 unknown。
問題 3:TypeScript 的型別在執行期還在嗎?
不在。型別在編譯時被抹除(type erasure),產出的是純 JS。所以不能在執行期用型別做判斷(要判斷得靠 typeof、instanceof 等執行期手段)。
問題 4:為什麼一定要開 strict?
strict 開啟 strictNullChecks 等關鍵檢查,能抓出大量 null/undefined 錯誤——這正是 TS 最有價值的地方。關掉等於只拿到半套保護。
問題 5:字面值 union 和 enum 怎麼選?
字面值 union("a" | "b")更輕量、無執行期產物,多數情況更受青睞;需要具名常數集合、或要在執行期列舉值時才用 enum。
問題 6:Node.js / Next.js 是 TypeScript 嗎?
不是。TypeScript 是語言、Node.js 是執行環境(runtime)、Next.js / React 是框架 / 函式庫——三者不同層次。Node.js、Next.js、React 都可以「用 TypeScript 來寫」,但它們本身不是 TS。詳見 TS / JavaScript / Node.js / 框架的關係。
總結
核心要點
- TS = JavaScript + 靜態型別;型別編譯期存在、執行期被抹除
- 善用型別推論,只在邊界手動標註
interface(物件/類別、可合併)vstype(union/tuple/運算)- Union
|(或,需收窄)/ Intersection&(且,合併) - 型別收窄(typeof/instanceof/in/可辨識聯合)讓 union 變精確
unknown優於any;never表不可能;斷言/!是繞過檢查要慎用- 新專案一律開
strict
快速參考
| 概念 | 重點 |
|---|---|
any / unknown / never |
關閉檢查 / 未知需收窄 / 不可能有值 |
interface vs type |
形狀可合併 vs union/運算 |
| / & |
union(或)/ intersection(且) |
| 收窄 | typeof / instanceof / in / 可辨識聯合 |
斷言 as / ! |
繞過檢查,慎用 |
strict |
必開,含 strictNullChecks |
建立日期:2026-06-18