掌握 TypeScript
從零到精通

提供豐富的線上TypeScript教學資源網站,含Typescript 用途、Typescript格式、語法、範例、Typescript 新手指南、 Typescript UI,讓你的 JavaScript 技能全面升級。

20+
教學章節
100+
程式碼範例
TS 5.x
最新版本
繁中
完整翻譯
🔒

靜態型別檢查

在執行程式前就發現型別錯誤,讓你的程式碼更安全可靠,減少 bug 的發生。

🧠

智慧提示支援

完整的 IDE 支援,讓開發效率大幅提升,享受更好的自動補全和錯誤提示體驗。

🏗️

物件導向設計

支援類別、介面、泛型等 OOP 特性,適合大型專案的架構設計與維護。

與 JS 完全相容

TypeScript 是 JavaScript 的超集,所有合法的 JS 程式碼都是合法的 TS 程式碼。

🛠️

強大工具型別

內建 Partial、Required、Pick 等工具型別,讓複雜的型別操作變得簡單。

🌐

生態系廣泛

React、Angular、Vue、NestJS 等主流框架都原生支援 TypeScript 開發。

✨ 你的第一個 TypeScript 程式

📄 hello.ts
// 這是 TypeScript!
// 型別注解讓程式碼更清晰

function 問候(姓名: string, 年齡: number): string {
  return `你好,${姓名}!你今年 ${年齡} 歲。`;
}

const 訊息 = 問候("小明", 25);
console.log(訊息);

// TypeScript 會在這裡報錯 ✗
// 問候(123, "二十五");  // 型別錯誤!
▶ 輸出結果
你好,小明!你今年 25 歲。

什麼是 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

第三步:初始化專案

📁 tsconfig.json
# 建立新專案目錄
mkdir my-ts-project
cd my-ts-project

# 初始化 TypeScript 設定
tsc --init
⚙️ tsconfig.json 推薦設定
{
  "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"]
}
推薦編輯器:使用 Visual Studio Code(免費),它內建對 TypeScript 的完整支援,包括錯誤高亮、智慧補全和即時型別檢查。

TypeScript 基本型別

掌握 TypeScript 的型別系統基礎,了解各種內建型別的使用方式。

原始型別 (Primitive Types)

string
number
boolean
null
undefined
symbol
bigint
📄 primitive-types.ts
// ===== 字串型別 (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;

特殊型別

📄 special-types.ts
// ===== 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 中正確地宣告變數並加上型別注解。

三種宣告方式

📄 variables.ts
// ===== 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(有作用域問題)。

型別注解語法

📄 type-annotations.ts
// 基本語法:變數名稱: 型別 = 初始值
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 可以自動推斷變數的型別,不需要每次都明確標注。

自動型別推斷

📄 type-inference.ts
// 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)

📄 arrays.ts
// 兩種宣告陣列的方式
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)

元組讓你定義一個固定長度且各位置有固定型別的陣列。

📄 tuples.ts
// 定義元組 - 固定位置、固定型別
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)

使用列舉來定義一組相關的命名常數,讓程式碼更具可讀性和維護性。

📄 enums.ts
// ===== 數字列舉(預設,從 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-intersection.ts
// ===== 聯合型別 (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 中定義型別安全的函式,包括各種參數類型和函式多載。

📄 functions.ts
// ===== 基本函式型別 =====
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 最強大的特性之一,用於定義物件的結構合約。

📄 interfaces.ts
// ===== 基本介面 =====
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 vs Type Alias:兩者功能相似,但 interface 可以被合併和繼承,更適合定義物件結構;type 更適合定義複雜的型別計算和聯合型別。

類別 (Class)

TypeScript 的類別支援完整的物件導向程式設計,包括繼承、存取修飾子和抽象類別。

📄 classes.ts
// ===== 完整類別範例 =====
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 最強大的功能之一。

📄 generics.ts
// ===== 泛型函式 =====
// 問題:不用泛型時
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)

掌握條件型別、映射型別、模板字面量型別等進階功能,讓你的型別系統更加強大。

📄 advanced-types.ts
// ===== 條件型別 (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 內建的強大工具型別,讓型別轉換變得簡單。

📄 utility-types.ts
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]
Partial<T>
所有屬性變可選 (?)
Required<T>
所有屬性變必填
Readonly<T>
所有屬性變唯讀
Pick<T, K>
選取指定屬性
Omit<T, K>
排除指定屬性
Record<K, V>
建立鍵值對型別

模組系統 (Modules)

組織你的 TypeScript 程式碼成可重用的模組。

📄 modules.ts
// ===== 命名匯出 (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 的穩定裝飾器功能,讓你優雅地擴展類別和方法的行為。

📄 decorators.ts
// ===== 類別裝飾器 =====
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 常用語法,方便開發時快速查閱。

變數宣告
let 名稱: string = "值"; const 常數: number = 42; let 聯合: string | number;
函式
function fn(a: T): R { } const fn = (a: T): R => expr; function fn(a?: T): void { }
介面
interface 使用者 { id: number; 姓名?: string; // 可選 readonly url: string; }
型別別名
type ID = string | number; type 使用者 = { 姓名: string }; type 方向 = "上"|"下"|"左"|"右";
泛型
function 身分<T>(arg: T): T { } interface 盒子<T> { 值: T; } type 回應<T = any> = { 資料: T };
工具型別
Partial<T> // 全可選 Required<T> // 全必填 Readonly<T> // 全唯讀 Pick<T, K> // 選取屬性 Omit<T, K> // 排除屬性
類別
class 動物 { private 名: string; constructor(名: string) { } 叫(): void { } static 建立() { } }
列舉
enum 方向 { 上, 下, 左, 右 } enum 顏色 { 紅 = "RED", 綠 = "GREEN" }
型別斷言
let x = 值 as string; let y = <string>值; let z = 值!; // Non-null 斷言
模組
export { 甲, 乙 }; export default 丙; import { 甲 } from './module'; import 丙 from './module';

🔥 常見型別縮窄技巧

型別縮窄大全
// 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