TypeScript 基礎概念(Basics)
TypeScript 是什麼、與 JavaScript 的差異
簡介
在前端與 Node.js 生態系統裡,JavaScript 仍是唯一的執行語言,但隨著專案規模日益增長,純 JavaScript 的動態型別特性常常會帶來維護成本與錯誤風險。TypeScript(TS)正是為了解決這些問題而誕生的,它在 JavaScript 基礎上加入了靜態型別系統與編譯期檢查,讓開發者能在編寫程式碼時即時捕獲大部分常見錯誤。
本單元將說明 TypeScript 的定位、它與 JavaScript 的主要差異,並透過實作範例展示如何在日常開發中逐步導入型別安全。即使你是剛接觸前端的新人,或是已有多年 JavaScript 經驗的中階開發者,這篇文章都能幫助你快速掌握 TypeScript 的核心概念,為後續進階主題(如泛型、模組、裝飾器)奠定基礎。
核心概念
1. 型別系統的基本構成
| 類別 | 說明 | 範例 |
|---|---|---|
| 基本型別 | number、string、boolean、null、undefined、symbol、bigint |
let age: number = 30; |
| 陣列與元組 | 陣列可用 type[] 或 Array<type>;元組允許固定長度與不同型別 |
let nums: number[] = [1,2,3];let point: [number, number] = [10, 20]; |
| 列舉 (enum) | 為具名常數集合提供可讀性 | enum Direction { Up, Down, Left, Right } |
| 介面 (interface) | 定義物件結構與合約 | interface Person { name: string; age?: number; } |
| 類別 (class) | 支援 ES6 class 並加入型別檢查 | class Animal { constructor(public name: string) {} } |
| 聯合與交叉型別 | ` | 表示「或」;&` 表示「且」 |
| 泛型 (generic) | 讓型別在使用時再決定 | function identity<T>(arg: T): T { return arg; } |
重點:所有 TypeScript 程式碼在執行前都會被 編譯 (transpile) 成純 JavaScript,編譯過程會檢查型別相容性,若有錯誤則會阻止產出可執行檔。
2. 型別推論 (Type Inference)
即使沒有明確寫出型別,編譯器也會根據賦值內容自動推斷。例如:
let message = "Hello TypeScript!"; // 推論為 string
const count = 5; // 推論為 5 (字面量型別)
let mixed = [1, "two", true]; // 推論為 (string | number | boolean)[]
技巧:在需要更嚴謹的型別時,仍建議手動加上註解,尤其是函式的參數與回傳值,這樣才能充分發揮編譯器的檢查能力。
3. 型別宣告與註解
// 基本型別宣告
let username: string = "alice";
let isAdmin: boolean = false;
// 物件型別
interface Product {
id: number;
name: string;
price: number;
tags?: string[]; // 可選屬性
}
const book: Product = {
id: 101,
name: "TypeScript 入門",
price: 399,
};
4. 函式的型別檢查
// 具名函式
function add(a: number, b: number): number {
return a + b;
}
// 匿名函式 (箭頭函式) + 型別推論
const greet = (name: string): void => {
console.log(`Hello, ${name}!`);
};
注意:若省略回傳型別,編譯器會根據
return表達式自動推論。但在較複雜的函式(例如多條件回傳)時,手動標註回傳型別能避免意外的any推論。
5. any 與 unknown
| 型別 | 說明 | 何時使用 |
|---|---|---|
any |
完全放棄型別檢查,等同於 JavaScript 原始行為 | 快速遷移大型 JS 專案時的暫時措施 |
unknown |
必須先做型別檢查才能使用,較安全的 any 替代 |
接收外部 API、JSON 解析等不確定資料時 |
let data: unknown = JSON.parse('{"x":10}');
if (typeof data === "object" && data !== null && "x" in data) {
const x = (data as { x: number }).x; // 必須斷言
console.log(x);
}
6. 型別斷言 (Type Assertion)
有時候我們確定某個變數的實際型別,卻因為編譯器無法推斷,需要手動告訴編譯器:
let el = document.getElementById("app") as HTMLDivElement;
el.style.backgroundColor = "lightblue";
警告:過度使用斷言會削弱 TypeScript 的安全性,僅在真的無法讓編譯器自行推斷時才使用。
程式碼範例
下面提供 5 個實務上常用 的 TypeScript 範例,從基本型別到泛型都有涵蓋,並在每段程式碼後加入說明。
範例 1:基本型別與函式
// 1️⃣ 基本型別宣告
let id: number = 12345;
let title: string = "TypeScript 入門";
let isPublished: boolean = true;
// 2️⃣ 函式簽名
function formatTitle(id: number, title: string): string {
return `[${id}] ${title}`;
}
// 3️⃣ 呼叫
const result = formatTitle(id, title);
console.log(result); // [12345] TypeScript 入門
說明:這段程式展示了 型別註解、函式參數與回傳值 的寫法,編譯器會保證 id 必須是 number,若傳入字串會在編譯階段報錯。
範例 2:介面與可選屬性
interface User {
id: number;
name: string;
email?: string; // 可選屬性
}
// 建立符合介面的物件
const alice: User = {
id: 1,
name: "Alice",
email: "alice@example.com",
};
const bob: User = {
id: 2,
name: "Bob", // 沒有 email 也是合法的
};
說明:email? 表示此屬性可以不存在,使用介面可以在大型專案中統一物件結構,減少錯字或遺漏欄位的問題。
範例 3:列舉 (Enum) 與字串映射
enum Role {
Admin = "ADMIN",
Editor = "EDITOR",
Viewer = "VIEWER",
}
function canEdit(role: Role): boolean {
return role === Role.Admin || role === Role.Editor;
}
// 測試
console.log(canEdit(Role.Admin)); // true
console.log(canEdit(Role.Viewer)); // false
說明:列舉讓 常數值 具備可讀性,同時在編譯期就能限制只能使用列舉成員,避免硬編碼字串導致的錯誤。
範例 4:泛型函式 – 取得陣列第一個元素
function firstElement<T>(arr: T[]): T | undefined {
return arr.length > 0 ? arr[0] : undefined;
}
// 使用不同型別的陣列
const num = firstElement([10, 20, 30]); // num: number | undefined
const str = firstElement(["a", "b", "c"]); // str: string | undefined
const mixed = firstElement([1, "two", true]); // mixed: number | string | boolean | undefined
說明:<T> 表示 泛型參數,在呼叫時會根據實際傳入的陣列自動推斷 T 為 number、string 或其他型別,使函式具備 高度重用性。
範例 5:型別守護 (Type Guard) 與 unknown
function isString(value: unknown): value is string {
return typeof value === "string";
}
function printLength(value: unknown) {
if (isString(value)) {
// 在此分支中,value 已被縮窄為 string
console.log(`長度:${value.length}`);
} else {
console.log("不是字串,無法取得長度");
}
}
// 測試
printLength("Hello"); // 長度:5
printLength(123); // 不是字串,無法取得長度
說明:value is string 為 型別守護,讓編譯器在條件分支內推斷 value 為 string,從而安全存取 .length。這是處理 unknown 或外部資料的常見技巧。
常見陷阱與最佳實踐
| 陷阱 | 描述 | 最佳實踐 |
|---|---|---|
過度使用 any |
失去型別檢查的好處,等同回到純 JavaScript。 | 盡量在 tsconfig.json 中開啟 noImplicitAny,僅在最後一步才允許 any。 |
忽略 strictNullChecks |
null / undefined 會被視為所有型別的子類別,容易產生執行時錯誤。 |
啟用 strictNullChecks,在變數上明確標註 ` |
| 型別斷言濫用 | 直接把 any 斷言成目標型別,會讓錯誤在執行階段才顯現。 |
只在確定資料來源可靠且編譯器無法推斷時使用,並配合跑測試。 |
未設定 tsconfig.json |
預設編譯選項過於寬鬆,易忽略潛在問題。 | 建立嚴格模式的 tsconfig.json(strict: true、noImplicitAny、noUnusedLocals 等)。 |
| 相容性問題 | 直接引用第三方未提供型別宣告的 JavaScript 套件會產生 any。 |
使用 @types 社群套件或自行撰寫宣告檔 (.d.ts)。 |
建議的 tsconfig.json 範本
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"strict": true, // 開啟所有嚴格檢查
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"sourceMap": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
實際應用場景
前端框架 (React / Vue / Angular)
- 使用 TypeScript 能為 Component Props、State、Hooks 提供完整型別,減少 UI 變更時的破壞性錯誤。
- 例如在 React 中,
useState<number>(0)明確指出狀態只能是number,避免因不小心寫成字串導致渲染失敗。
Node.js 後端服務
- Express、Koa 等框架的請求/回應物件可透過介面描述,使路由處理函式的參數型別一致。
- 結合 ORM(如 TypeORM)時,資料模型本身即是 TypeScript 類別,編譯期即可檢查欄位名稱與型別。
大型單頁應用 (SPA) 或微前端
- 隨著程式碼量增長,跨模組的型別共享(透過
export type、declare module)可避免不同團隊之間的型別不一致。
- 隨著程式碼量增長,跨模組的型別共享(透過
開發套件與函式庫
- 若你要發佈 npm 套件,提供
.d.ts型別宣告會大幅提升使用者體驗,讓使用者在 IDE 中即能得到自動完成與錯誤提示。
- 若你要發佈 npm 套件,提供
與第三方 API 的互動
- 透過
fetch取得的 JSON 資料往往是any,使用 型別守護 或 zod / yup 等驗證庫可在取得資料後立即轉換為安全型別。
- 透過
總結
- TypeScript 是 JavaScript 的超集,透過靜態型別、編譯期檢查與豐富的語法特性,讓開發者在寫程式時就能捕捉多數錯誤。
- 與純 JavaScript 的差異核心在於 型別系統、編譯階段的嚴格檢查,以及 工具鏈支援(如
tsc、ESLint、IDE 智慧提示)。 - 本文介紹了 基本型別、介面、列舉、泛型、型別守護 等概念,並提供 5 個實務範例,示範如何在日常開發中運用 TypeScript。
- 為避免常見陷阱,建議在專案一開始就設定 嚴格模式(
strict: true),盡量減少any、善用unknown與型別守護,並配合 單元測試 形成雙重防護。 - 無論是前端框架、Node.js 服務、或是開發公共套件,TypeScript 都能提升程式碼的可讀性、可維護性與可靠性。
掌握了以上基礎,你已經具備在任何 JavaScript 專案中安全導入 TypeScript 的能力。接下來可以進一步探索 模組系統、命名空間、進階泛型、映射型別 等更高階的主題,讓你的程式碼品質持續向上提升。祝你在 TypeScript 的學習旅程中玩得開心、寫得安心!