掌握 TypeScript
從零到精通
提供豐富的線上TypeScript教學資源網站,含Typescript 用途、Typescript格式、語法、範例、Typescript 新手指南、 Typescript UI,讓你的 JavaScript 技能全面升級。
靜態型別檢查
在執行程式前就發現型別錯誤,讓你的程式碼更安全可靠,減少 bug 的發生。
智慧提示支援
完整的 IDE 支援,讓開發效率大幅提升,享受更好的自動補全和錯誤提示體驗。
物件導向設計
支援類別、介面、泛型等 OOP 特性,適合大型專案的架構設計與維護。
與 JS 完全相容
TypeScript 是 JavaScript 的超集,所有合法的 JS 程式碼都是合法的 TS 程式碼。
強大工具型別
內建 Partial、Required、Pick 等工具型別,讓複雜的型別操作變得簡單。
生態系廣泛
React、Angular、Vue、NestJS 等主流框架都原生支援 TypeScript 開發。
✨ 你的第一個 TypeScript 程式
// 這是 TypeScript!
// 型別注解讓程式碼更清晰
function 問候(姓名: string, 年齡: number): string {
return `你好,${姓名}!你今年 ${年齡} 歲。`;
}
const 訊息 = 問候("小明", 25);
console.log(訊息);
// TypeScript 會在這裡報錯 ✗
// 問候(123, "二十五"); // 型別錯誤!
什麼是 TypeScript?
了解 TypeScript 的誕生背景、核心概念,以及為什麼它成為現代 Web 開發的首選語言。
TypeScript 簡介
TypeScript 是由 Microsoft 於 2012 年開發的開源程式語言,它是 JavaScript 的超集(Superset),這意味著所有合法的 JavaScript 程式碼,在 TypeScript 中都是合法的。
TypeScript 最大的特點是加入了靜態型別系統(Static Type System),讓開發者能在程式執行前就發現潛在的型別錯誤,大幅提升程式碼品質和開發效率。
提早發現錯誤
在編譯階段就能捕獲型別錯誤,而不是等到執行時才崩潰,節省大量除錯時間。
自我說明的程式碼
型別注解就像是文件,讓其他開發者(包括未來的你)更容易理解程式碼的意圖。
強大的重構支援
IDE 能精準地追蹤所有型別使用,讓重構操作更加安全可靠,不怕改漏改錯。
更好的可擴展性
隨著專案規模增大,TypeScript 的型別系統能有效管理複雜的程式碼關係。
| 特性 | JavaScript | TypeScript |
|---|---|---|
| 型別系統 | 動態型別 | 靜態型別(可選) |
| 錯誤發現時機 | 執行時 | 編譯時(更早發現) |
| IDE 支援 | 基本支援 | 完整智慧提示 |
| 介面支援 | ❌ | ✅ 完整支援 |
| 泛型 | ❌ | ✅ 完整支援 |
| 裝飾器 | 實驗性 | ✅ 成熟支援 |
| 學習曲線 | 較低 | 略高(但值得) |
Angular
Google 的 Angular 框架從一開始就使用 TypeScript 開發,是最大的 TS 使用者。
React
Meta 的 React 官方支援 TypeScript,@types/react 是最受歡迎的型別定義包之一。
Vue.js
Vue 3 以 TypeScript 重寫核心,並提供完整的 TypeScript 支援。
NestJS
後端框架 NestJS 原生使用 TypeScript,深度整合型別系統。
TypeScript 的工作原理
TypeScript 程式碼需要被編譯(Transpile)成 JavaScript 後才能執行。這個過程由 TypeScript 編譯器(tsc)完成:
// 📝 TypeScript 原始碼 (app.ts)
interface 使用者 {
姓名: string;
年齡: number;
}
function 顯示使用者(使用者: 使用者): void {
console.log(`姓名: ${使用者.姓名}, 年齡: ${使用者.年齡}`);
}
const 我: 使用者 = { 姓名: "小華", 年齡: 28 };
顯示使用者(我);
tsc app.ts 可將 TypeScript 編譯成 JavaScript。使用 tsc --watch 可以監視檔案變化自動重新編譯。
安裝 TypeScript 環境
快速設定你的 TypeScript 開發環境,只需幾個步驟即可開始撰寫型別安全的程式碼。
第一步:安裝 Node.js
TypeScript 需要 Node.js 環境。請前往 nodejs.org 下載並安裝最新的 LTS 版本。
# 確認 Node.js 版本
node --version # v20.x.x
# 確認 npm 版本
npm --version # 10.x.x
第二步:安裝 TypeScript
# 全域安裝 TypeScript(推薦)
npm install -g typescript
# 確認安裝版本
tsc --version # Version 5.x.x
# 或在專案中安裝
npm install --save-dev typescript
第三步:初始化專案
# 建立新專案目錄
mkdir my-ts-project
cd my-ts-project
# 初始化 TypeScript 設定
tsc --init
{
"compilerOptions": {
"target": "ES2020", // 編譯目標 JS 版本
"module": "commonjs", // 模組系統
"strict": true, // 啟用嚴格模式(強烈推薦)
"outDir": "./dist", // 輸出目錄
"rootDir": "./src", // 原始碼目錄
"sourceMap": true, // 產生 Source Map
"esModuleInterop": true, // 改善模組相容性
"resolveJsonModule": true // 允許匯入 JSON
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
TypeScript 基本型別
掌握 TypeScript 的型別系統基礎,了解各種內建型別的使用方式。
原始型別 (Primitive Types)
// ===== 字串型別 (string) =====
let 姓名: string = "小明";
let 問候語: string = `你好,${姓名}!`;
// ===== 數字型別 (number) =====
let 整數: number = 42;
let 浮點數: number = 3.14;
let 十六進位: number = 0xFF;
let 二進位: number = 0b1010;
// ===== 布林型別 (boolean) =====
let 已登入: boolean = true;
let 已驗證: boolean = false;
// ===== null 和 undefined =====
let 空值: null = null;
let 未定義: undefined = undefined;
// ===== Symbol (唯一識別符) =====
const 唯一ID: symbol = Symbol("id");
// ===== BigInt (大整數) =====
const 巨大數字: bigint = 9007199254740991n;
特殊型別
// ===== any - 停用型別檢查(謹慎使用!)=====
let 任意值: any = "文字";
任意值 = 42; // ✅ OK
任意值 = true; // ✅ OK - any 型別允許任何值
// ===== unknown - 比 any 更安全的未知型別 =====
let 未知值: unknown = "可能是任何東西";
// 必須先做型別檢查才能使用
if (typeof 未知值 === "string") {
console.log(未知值.toUpperCase()); // ✅ 安全
}
// ===== void - 沒有返回值的函式 =====
function 打招呼(): void {
console.log("你好!");
// 沒有 return 或 return undefined
}
// ===== never - 永遠不會返回的函式 =====
function 拋出錯誤(訊息: string): never {
throw new Error(訊息);
}
// ===== object - 非原始型別 =====
let 物件: object = { 名稱: "測試" };
// ===== 型別斷言 =====
let 某值: unknown = "TypeScript 很棒";
let 字串長度 = (某值 as string).length; // 告訴 TS 這是 string
console.log(字串長度); // 13
型別表格速覽
| 型別 | 說明 | 範例 | 使用場景 |
|---|---|---|---|
string | 文字字串 | "Hello" | 文字處理 |
number | 所有數字 | 42, 3.14 | 數值計算 |
boolean | 真假值 | true, false | 條件判斷 |
any | 停用型別檢查 | 任何值 | 遷移舊程式碼 |
unknown | 未知型別(安全) | 需先判斷 | 外部輸入 |
void | 無返回值 | undefined | 函式返回型別 |
never | 永不返回 | — | 錯誤處理 |
變數宣告與型別注解
學習如何在 TypeScript 中正確地宣告變數並加上型別注解。
三種宣告方式
// ===== let - 可重新賦值 =====
let 計數: number = 0;
計數 = 1; // ✅ OK
// 計數 = "一"; // ❌ 錯誤: 不能把 string 賦值給 number
// ===== const - 常數,不可重新賦值 =====
const PI: number = 3.14159;
// PI = 3; // ❌ 錯誤: 無法指派給常數
// ===== var - 舊式宣告(不推薦使用)=====
var 舊式變數: string = "避免使用 var";
// ===== 多重宣告 =====
let x: number, y: number, z: number;
x = 1; y = 2; z = 3;
// ===== 解構賦值 =====
const [a, b, c]: [number, number, number] = [1, 2, 3];
const { 姓名, 年齡 }: { 姓名: string; 年齡: number } = {
姓名: "小明",
年齡: 25
};
const,需要重新賦值時才用 let,避免使用 var(有作用域問題)。
型別注解語法
// 基本語法:變數名稱: 型別 = 初始值
let 變數名稱: string = "初始值";
// 只宣告,稍後賦值
let 稍後賦值: number;
稍後賦值 = 100;
// 聯合型別 - 可以是多種型別之一
let 彈性值: string | number = "文字";
彈性值 = 42; // 也可以是數字
// 可為 null 的值(啟用 strictNullChecks 後)
let 可能為空: string | null = null;
可能為空 = "現在有值了";
// 只讀屬性
const 設定 = {
API_URL: "https://api.example.com",
逾時時間: 5000,
} as const; // 讓所有屬性都變成 readonly
// 型別中使用 typeof
const 範本 = { 姓名: "小明", 分數: 100 };
type 使用者型別 = typeof 範本; // { 姓名: string; 分數: number }
型別推斷 (Type Inference)
TypeScript 可以自動推斷變數的型別,不需要每次都明確標注。
自動型別推斷
// TypeScript 自動推斷為 string
let 城市 = "台北"; // 推斷型別:string
// 城市 = 100; // ❌ 錯誤!
// TypeScript 自動推斷為 number
let 溫度 = 28; // 推斷型別:number
// 函式返回值推斷
function 加法(a: number, b: number) {
return a + b; // 推斷返回型別:number
}
// 陣列型別推斷
let 水果 = ["蘋果", "香蕉", "橘子"]; // string[]
let 數字 = [1, 2, 3]; // number[]
let 混合 = [1, "兩", true]; // (string | number | boolean)[]
// 物件型別推斷
let 使用者 = {
姓名: "小明", // 推斷 string
年齡: 25, // 推斷 number
活躍: true // 推斷 boolean
};
// 等同於:{ 姓名: string; 年齡: number; 活躍: boolean }
// 最佳通用型別
let 混合陣列 = [0, 1, null]; // 推斷為 (number | null)[]
陣列與元組
在 TypeScript 中安全地操作陣列,以及使用元組定義固定結構的資料。
陣列型別 (Array Types)
// 兩種宣告陣列的方式
let 城市: string[] = ["台北", "台中", "高雄"];
let 分數: Array<number> = [85, 92, 78, 96];
// 唯讀陣列(不可修改)
const 常數陣列: readonly number[] = [1, 2, 3];
// 常數陣列.push(4); // ❌ 錯誤!
// 聯合型別陣列
let 混合: (string | number)[] = ["一", 2, "三", 4];
// 二維陣列
let 矩陣: number[][] = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// 陣列解構
const [第一, 第二, ...其他] = 城市;
console.log(第一); // "台北"
console.log(其他); // ["高雄"]
// 型別安全的陣列操作
城市.push("台南"); // ✅ OK
// 城市.push(123); // ❌ 錯誤:不是 string
const 長度: number = 城市.length;
const 第一個: string = 城市[0];
元組型別 (Tuple Types)
元組讓你定義一個固定長度且各位置有固定型別的陣列。
// 定義元組 - 固定位置、固定型別
let 個人資料: [string, number, boolean] = ["小明", 25, true];
let [姓名, 年齡, 已驗證] = 個人資料;
// 具名元組(TypeScript 4.0+)
type RGB = [red: number, green: number, blue: number];
const 藍色: RGB = [0, 0, 255];
const 綠色: RGB = [0, 128, 0];
// 可選元素
type 座標 = [number, number, number?];
const 2D座標: 座標 = [10, 20];
const 3D座標: 座標 = [10, 20, 30];
// 剩餘元素
type 字串加數字 = [string, ...number[]];
const 數據: 字串加數字 = ["分數", 100, 95, 87];
// 實際應用:useState 類似的回傳值
function 使用計數器(初始值: number): [number, () => void, () => void] {
let 計數 = 初始值;
const 增加 = () => { 計數++; };
const 減少 = () => { 計數--; };
return [計數, 增加, 減少];
}
const [數量, 加, 減] = 使用計數器(0);
列舉 (Enum)
使用列舉來定義一組相關的命名常數,讓程式碼更具可讀性和維護性。
// ===== 數字列舉(預設,從 0 開始)=====
enum 方向 {
上, // 0
下, // 1
左, // 2
右 // 3
}
let 移動方向: 方向 = 方向.上;
console.log(移動方向); // 0
console.log(方向[0]); // "上"(反向對應)
// ===== 自定義數字起始值 =====
enum HTTP狀態碼 {
成功 = 200,
已建立 = 201,
錯誤請求 = 400,
未授權 = 401,
找不到 = 404,
伺服器錯誤 = 500
}
function 處理回應(狀態: HTTP狀態碼) {
if (狀態 === HTTP狀態碼.成功) {
console.log("請求成功!");
} else if (狀態 === HTTP狀態碼.找不到) {
console.log("資源不存在");
}
}
// ===== 字串列舉(推薦使用!更清晰)=====
enum 角色 {
管理員 = "ADMIN",
編輯 = "EDITOR",
訪客 = "GUEST"
}
const 當前角色: 角色 = 角色.管理員;
console.log(當前角色); // "ADMIN"
// ===== Const 列舉(最佳效能)=====
const enum 顏色 {
紅 = "RED",
綠 = "GREEN",
藍 = "BLUE"
}
const 我的顏色 = 顏色.紅; // 編譯後直接變成 "RED"
聯合型別與交叉型別
組合多種型別,讓你的型別定義更靈活且更精確。
// ===== 聯合型別 (Union Types) - 用 | =====
type 字串或數字 = string | number;
let ID: 字串或數字 = "abc123";
ID = 456; // 也可以
// 型別縮窄 (Type Narrowing)
function 格式化ID(id: string | number): string {
if (typeof id === "string") {
return id.toUpperCase(); // 這裡 TS 知道是 string
}
return id.toString(); // 這裡 TS 知道是 number
}
// 字面量聯合型別
type 顏色 = "紅" | "綠" | "藍";
type 尺寸 = "S" | "M" | "L" | "XL";
function 設定主題(顏色: 顏色): void {
console.log(`設定為${顏色}色主題`);
}
設定主題("紅"); // ✅ OK
// 設定主題("黃"); // ❌ 錯誤!
// ===== 交叉型別 (Intersection Types) - 用 & =====
type 可名稱 = { 姓名: string };
type 可年齡 = { 年齡: number };
type 人員 = 可名稱 & 可年齡; // 必須同時有兩個屬性
const 員工: 人員 = {
姓名: "小華",
年齡: 30
};
// 實際應用:混入模式
type 可序列化 = { 序列化(): string };
type 可列印 = { 列印(): void };
type 文件 = 人員 & 可序列化 & 可列印;
// ===== 可辨識聯合 (Discriminated Union) =====
type 圓形 = { 形狀: "圓形"; 半徑: number };
type 長方形 = { 形狀: "長方形"; 寬: number; 高: number };
type 圖形 = 圓形 | 長方形;
function 計算面積(圖形: 圖形): number {
switch (圖形.形狀) {
case "圓形":
return Math.PI * 圖形.半徑 ** 2;
case "長方形":
return 圖形.寬 * 圖形.高;
}
}
TypeScript 函式
了解如何在 TypeScript 中定義型別安全的函式,包括各種參數類型和函式多載。
// ===== 基本函式型別 =====
function 加法(a: number, b: number): number {
return a + b;
}
// ===== 箭頭函式 =====
const 乘法 = (a: number, b: number): number => a * b;
// ===== 可選參數(用 ? 標記)=====
function 建立使用者(姓名: string, 年齡?: number): string {
if (年齡 !== undefined) {
return `${姓名}(${年齡}歲)`;
}
return 姓名;
}
建立使用者("小明"); // ✅ OK
建立使用者("小明", 25); // ✅ OK
// ===== 預設參數 =====
function 打招呼(
姓名: string,
語言: string = "中文",
正式: boolean = false
): string {
const 問候 = 語言 === "中文" ? "你好" : "Hello";
return `${問候},${姓名}!`;
}
// ===== 剩餘參數 =====
function 加總(...數字: number[]): number {
return 數字.reduce((acc, val) => acc + val, 0);
}
console.log(加總(1, 2, 3, 4, 5)); // 15
// ===== 函式型別定義 =====
type 計算函式 = (a: number, b: number) => number;
const 除法: 計算函式 = (a, b) => a / b;
// ===== 函式多載 (Overloading) =====
function 格式化(值: string): string;
function 格式化(值: number): string;
function 格式化(值: boolean): string;
function 格式化(值: string | number | boolean): string {
if (typeof 值 === "string") return `"${值}"`;
if (typeof 值 === "number") return 值.toFixed(2);
return 值 ? "是" : "否";
}
console.log(格式化("hello")); // "hello"
console.log(格式化(3.14159)); // 3.14
console.log(格式化(true)); // 是
介面 (Interface)
介面是 TypeScript 最強大的特性之一,用於定義物件的結構合約。
// ===== 基本介面 =====
interface 使用者 {
readonly id: number; // 唯讀屬性
姓名: string;
電子郵件: string;
電話?: string; // 可選屬性
建立時間: Date;
}
const 新使用者: 使用者 = {
id: 1,
姓名: "小明",
電子郵件: "[email protected]",
建立時間: new Date()
};
// ===== 介面繼承 =====
interface 動物 {
名稱: string;
叫聲(): string;
}
interface 寵物 extends 動物 {
主人: string;
已接種疫苗: boolean;
}
interface 導盲犬 extends 寵物 {
訓練等級: number;
}
// ===== 介面合併(同名自動合併)=====
interface 設定 { 主題: string; }
interface 設定 { 語言: string; }
// 等同於:interface 設定 { 主題: string; 語言: string; }
// ===== 函式介面 =====
interface 搜尋函式 {
(來源: string, 關鍵字: string): boolean;
}
const 搜尋: 搜尋函式 = (來源, 關鍵字) => {
return 來源.includes(關鍵字);
};
// ===== 索引簽名 =====
interface 字典 {
[key: string]: string | number;
長度: number; // 必須符合索引型別
}
// ===== 泛型介面 =====
interface 回應<T> {
資料: T;
狀態碼: number;
訊息: string;
時間戳記: Date;
}
type 使用者回應 = 回應<使用者>;
type 清單回應 = 回應<使用者[]>;
interface 可以被合併和繼承,更適合定義物件結構;type 更適合定義複雜的型別計算和聯合型別。
類別 (Class)
TypeScript 的類別支援完整的物件導向程式設計,包括繼承、存取修飾子和抽象類別。
// ===== 完整類別範例 =====
class 銀行帳戶 {
// 存取修飾子
readonly 帳號: string; // 只能讀,不能改
private _餘額: number; // 外部無法直接存取
protected 擁有者: string; // 子類別可存取
public 銀行名稱: string; // 預設,外部可存取
constructor(帳號: string, 擁有者: string, 初始金額: number = 0) {
this.帳號 = 帳號;
this.擁有者 = 擁有者;
this._餘額 = 初始金額;
this.銀行名稱 = "台灣銀行";
}
// Getter
get 餘額(): number {
return this._餘額;
}
// 存款方法
存款(金額: number): void {
if (金額 <= 0) throw new Error("金額必須大於 0");
this._餘額 += 金額;
console.log(`存款 ${金額} 元,目前餘額:${this._餘額}`);
}
// 提款方法
提款(金額: number): boolean {
if (金額 > this._餘額) {
console.log("餘額不足");
return false;
}
this._餘額 -= 金額;
return true;
}
// 靜態方法
static 建立新帳號(擁有者: string): 銀行帳戶 {
const 帳號 = Math.random().toString(36).slice(2, 10);
return new 銀行帳戶(帳號, 擁有者);
}
}
// ===== 繼承 =====
class 儲蓄帳戶 extends 銀行帳戶 {
private 利率: number;
constructor(帳號: string, 擁有者: string, 利率: number) {
super(帳號, 擁有者);
this.利率 = 利率;
}
計算利息(): number {
return this.餘額 * this.利率;
}
}
// ===== 抽象類別 =====
abstract class 圖形 {
abstract 計算面積(): number; // 子類別必須實作
abstract 計算周長(): number;
說明(): string {
return `面積:${this.計算面積()}, 周長:${this.計算周長()}`;
}
}
class 圓形 extends 圖形 {
constructor(private 半徑: number) { super(); }
計算面積(): number { return Math.PI * this.半徑 ** 2; }
計算周長(): number { return 2 * Math.PI * this.半徑; }
}
// ===== 實作介面 =====
interface 可序列化 {
序列化(): string;
}
class 使用者 implements 可序列化 {
constructor(public 姓名: string, public 年齡: number) {}
序列化(): string {
return JSON.stringify({ 姓名: this.姓名, 年齡: this.年齡 });
}
}
泛型 (Generics)
泛型讓你撰寫可重複使用且型別安全的程式碼,是 TypeScript 最強大的功能之一。
// ===== 泛型函式 =====
// 問題:不用泛型時
function 第一個元素_不安全(陣列: any[]): any {
return 陣列[0]; // 返回 any,失去型別資訊
}
// 解法:使用泛型 <T>
function 第一個元素<T>(陣列: T[]): T {
return 陣列[0]; // 返回型別與輸入一致
}
const 數字結果 = 第一個元素([1, 2, 3]); // 型別:number
const 字串結果 = 第一個元素(["a", "b"]); // 型別:string
// ===== 多型別參數 =====
function 配對<K, V>(鍵: K, 值: V): [K, V] {
return [鍵, 值];
}
const 配對結果 = 配對("name", 42); // [string, number]
// ===== 泛型約束 =====
interface 有長度 { length: number; }
function 顯示長度<T extends 有長度>(項目: T): T {
console.log(`長度: ${項目.length}`);
return 項目;
}
顯示長度("文字"); // ✅ string 有 length
顯示長度([1, 2, 3]); // ✅ array 有 length
// 顯示長度(123); // ❌ number 沒有 length
// ===== 泛型類別 =====
class 棧<T> {
private 資料: T[] = [];
推入(項目: T): void {
this.資料.push(項目);
}
彈出(): T | undefined {
return this.資料.pop();
}
查看頂端(): T | undefined {
return this.資料[this.資料.length - 1];
}
get 大小(): number {
return this.資料.length;
}
}
const 數字棧 = new 棧<number>();
數字棧.推入(1);
數字棧.推入(2);
數字棧.推入(3);
console.log(數字棧.彈出()); // 3
// ===== keyof 約束 =====
function 獲取屬性<T, K extends keyof T>(物件: T, 屬性: K): T[K] {
return 物件[屬性];
}
const 使用者 = { 姓名: "小明", 年齡: 25 };
const 姓名 = 獲取屬性(使用者, "姓名"); // 型別: string
// const 錯誤 = 獲取屬性(使用者, "電話"); // ❌ 錯誤!
// ===== 泛型預設值 =====
interface 分頁<T = any> {
資料: T[];
總數: number;
頁數: number;
每頁數量: number;
}
高階型別 (Advanced Types)
掌握條件型別、映射型別、模板字面量型別等進階功能,讓你的型別系統更加強大。
// ===== 條件型別 (Conditional Types) =====
type 非空 = string extends null ? never : string; // string
type 解包<T> = T extends Promise<infer U> ? U : T;
type 直接 = 解包<string>; // string
type 從Promise = 解包<Promise<number>>; // number
// ===== 映射型別 (Mapped Types) =====
type 所有可選<T> = { [P in keyof T]?: T[P] };
type 所有唯讀<T> = { readonly [P in keyof T]: T[P] };
type 所有可空<T> = { [P in keyof T]: T[P] | null };
interface 使用者 { 姓名: string; 年齡: number; 信箱: string; }
type 可選使用者 = 所有可選<使用者>;
// { 姓名?: string; 年齡?: number; 信箱?: string; }
// ===== 模板字面量型別 (Template Literal Types) =====
type 事件名稱<T extends string> = `on${Capitalize<T>}`;
type 按鈕事件 = 事件名稱<"click" | "hover" | "focus">;
// "onClick" | "onHover" | "onFocus"
type CSS屬性 = `${"margin" | "padding"}-${"top" | "bottom" | "left" | "right"}`;
// "margin-top" | "margin-bottom" | ... | "padding-right"
// ===== 型別守衛 (Type Guards) =====
interface 貓 { 種類: "貓"; 喵喵: () => void; }
interface 狗 { 種類: "狗"; 汪汪: () => void; }
type 寵物 = 貓 | 狗;
function 是貓(寵物: 寵物): 寵物 is 貓 {
return 寵物.種類 === "貓";
}
function 逗寵物(寵物: 寵物) {
if (是貓(寵物)) {
寵物.喵喵(); // TS 知道這是貓
} else {
寵物.汪汪(); // TS 知道這是狗
}
}
// ===== Infer 推斷 =====
type 函式返回型別<T> = T extends (...args: any[]) => infer R ? R : never;
function 取得使用者() { return { 姓名: "小明", 年齡: 25 }; }
type 使用者型別 = 函式返回型別<typeof 取得使用者>;
// { 姓名: string; 年齡: number }
工具型別 (Utility Types)
TypeScript 內建的強大工具型別,讓型別轉換變得簡單。
interface 使用者 {
id: number;
姓名: string;
信箱: string;
密碼: string;
年齡: number;
}
// ===== Partial<T> - 所有屬性變可選 =====
type 更新使用者 = Partial<使用者>;
// { id?: number; 姓名?: string; 信箱?: string; ... }
function 更新(id: number, 資料: Partial<使用者>) {
// 只需提供要更新的欄位
}
更新(1, { 姓名: "新名字" }); // ✅ OK
// ===== Required<T> - 所有屬性變必填 =====
type 完整使用者 = Required<使用者>;
// ===== Readonly<T> - 所有屬性變唯讀 =====
type 不可變使用者 = Readonly<使用者>;
// ===== Pick<T, K> - 選取特定屬性 =====
type 公開使用者 = Pick<使用者, "id" | "姓名" | "信箱">;
// 去掉密碼,只保留公開資訊
// ===== Omit<T, K> - 排除特定屬性 =====
type 無密碼使用者 = Omit<使用者, "密碼">;
// { id, 姓名, 信箱, 年齡 } - 移除密碼
// ===== Record<K, V> - 建立物件型別 =====
type 使用者目錄 = Record<string, 使用者>;
type 設定檔 = Record<"主題" | "語言" | "時區", string>;
const 設定: 設定檔 = {
主題: "深色",
語言: "zh-TW",
時區: "Asia/Taipei"
};
// ===== Exclude<T, U> - 從聯合型別中排除 =====
type 全部狀態 = "成功" | "失敗" | "等待" | "取消";
type 有效狀態 = Exclude<全部狀態, "取消">;
// "成功" | "失敗" | "等待"
// ===== Extract<T, U> - 提取符合的型別 =====
type 字串型別 = Extract<string | number | boolean, string>;
// string
// ===== NonNullable<T> - 移除 null 和 undefined =====
type 不可空字串 = NonNullable<string | null | undefined>;
// string
// ===== ReturnType<T> - 獲取函式返回型別 =====
function 取得設定() {
return { 主題: "深色", 版本: 2, 活躍: true };
}
type 設定型別 = ReturnType<typeof 取得設定>;
// { 主題: string; 版本: number; 活躍: boolean }
// ===== Parameters<T> - 獲取函式參數型別 =====
function 登入(信箱: string, 密碼: string, 記住: boolean) {}
type 登入參數 = Parameters<typeof 登入>;
// [信箱: string, 密碼: string, 記住: boolean]
模組系統 (Modules)
組織你的 TypeScript 程式碼成可重用的模組。
// ===== 命名匯出 (Named Export) =====
// 檔案: math.ts
export const PI = 3.14159;
export function 加法(a: number, b: number): number {
return a + b;
}
export interface 計算結果 {
值: number;
運算: string;
}
export type 數字陣列 = number[];
// ===== 預設匯出 (Default Export) =====
// 檔案: user.ts
export default class 使用者管理器 {
private 使用者: string[] = [];
新增(姓名: string): void {
this.使用者.push(姓名);
}
取得所有(): string[] {
return [...this.使用者];
}
}
// ===== 匯入 =====
// 命名匯入
import { PI, 加法, type 計算結果 } from './math';
// 預設匯入
import 使用者管理器 from './user';
// 重新命名匯入
import { 加法 as 數字加法 } from './math';
// 全部匯入
import * as 數學 from './math';
console.log(數學.PI); // 3.14159
// ===== 重新匯出 =====
// 檔案: index.ts(模組入口)
export { PI, 加法 } from './math';
export { default as 使用者管理器 } from './user';
export * from './utils';
// ===== 動態匯入 =====
async function 載入模組() {
const { 加法 } = await import('./math');
console.log(加法(1, 2)); // 3
}
// ===== 型別宣告檔案 =====
// 檔案: types.d.ts
declare module '*.svg' {
const 內容: string;
export default 內容;
}
declare global {
interface Window {
myApp: {
版本: string;
};
}
}
裝飾器 (Decorators)
TypeScript 5.0 的穩定裝飾器功能,讓你優雅地擴展類別和方法的行為。
// ===== 類別裝飾器 =====
function 封存(target: any) {
return class extends target {
readonly 建立時間 = new Date();
};
}
function 日誌(target: any) {
console.log(`建立類別: ${target.name}`);
return target;
}
@日誌
@封存
class 使用者 {
constructor(public 姓名: string) {}
}
// ===== 方法裝飾器 =====
function 計時(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const 原始方法 = descriptor.value;
descriptor.value = function(...args: any[]) {
const 開始 = performance.now();
const 結果 = 原始方法.apply(this, args);
const 結束 = performance.now();
console.log(`${propertyKey} 耗時: ${(結束 - 開始).toFixed(2)}ms`);
return 結果;
};
return descriptor;
}
function 記憶(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const 快取 = new Map();
const 原始 = descriptor.value;
descriptor.value = function(...args: any[]) {
const 鍵 = JSON.stringify(args);
if (快取.has(鍵)) return 快取.get(鍵);
const 結果 = 原始.apply(this, args);
快取.set(鍵, 結果);
return 結果;
};
}
class 計算器 {
@計時
@記憶
費波那契(n: number): number {
if (n <= 1) return n;
return this.費波那契(n - 1) + this.費波那契(n - 2);
}
}
// ===== 屬性裝飾器 =====
function 驗證(最小值: number, 最大值: number) {
return function(target: any, propertyKey: string) {
let 值: number;
Object.defineProperty(target, propertyKey, {
get: () => 值,
set: (新值: number) => {
if (新值 < 最小值 || 新值 > 最大值) {
throw new Error(`${propertyKey} 必須在 ${最小值}-${最大值} 之間`);
}
值 = 新值;
}
});
};
}
class 學生 {
@驗證(0, 100)
分數!: number;
constructor(public 姓名: string) {}
}
🧪 TypeScript 測驗挑戰
測試你的 TypeScript 知識,看看你掌握了多少!
🎯 第一題:型別基礎
以下哪個宣告會讓 TypeScript 報錯?
🎯 第二題:介面與型別
關於 TypeScript 的 interface,以下哪項說法是正確的?
🎯 第三題:泛型
以下泛型函式的輸出型別是什麼?function identity<T>(arg: T): T { return arg; }
當呼叫 identity(42) 時,返回型別是?
🎯 第四題:工具型別
若有 interface User { name: string; age: number; email: string; },使用 Omit<User, "email"> 的結果是?
📌 TypeScript 速查表
一頁掌握 TypeScript 常用語法,方便開發時快速查閱。
🔥 常見型別縮窄技巧
// typeof
if (typeof x === "string") { /* x 是 string */ }
// instanceof
if (x instanceof Date) { /* x 是 Date */ }
// in 操作符
if ("name" in user) { /* user 有 name 屬性 */ }
// 型別守衛函式
function isString(x: unknown): x is string {
return typeof x === "string";
}
// 可辨識聯合
switch (shape.kind) {
case "circle": /* 圓形 */ break;
case "rect": /* 矩形 */ break;
}
// 斷言函式
function assertString(x: unknown): asserts x is string {
if (typeof x !== "string") throw new Error("不是字串!");
}
assertString(value);
// 此行後 value 被視為 string