TypeScript 模組 (Modules)

在現代 JavaScript 和 TypeScript 開發中,處理程式碼組織的唯一標準就是 ES Modules (即 importexport)。

早期的 TypeScript 有所謂的 "Namespaces" (命名空間) 和 "Internal Modules",但這些現在都已經過時了。請忘記它們,擁抱 ES Modules。

匯出 (Export)

想要讓某個變數、函式或類別在其他檔案使用,就在前面加個 export

1. 具名匯出 (Named Export)

這是最推薦的方式,因為匯入時名稱必須相符,比較不容易出錯。

// math.ts
export const PI = 3.14;

export function add(a: number, b: number) {
  return a + b;
}

// 也可以這樣寫
const sub = (a: number, b: number) => a - b;
export { sub };

2. 預設匯出 (Default Export)

每個檔案只能有一個預設匯出。

// calculator.ts
export default class Calculator {
  // ...
}

最佳實踐:盡量少用 default export。因為 default export 在匯入時可以隨意命名,這會導致重構困難 (例如你要把 Calculator 改名,IDE 很難幫你自動更新所有引用)。

匯入 (Import)

1. 匯入具名匯出

// app.ts
import { PI, add } from './math';

console.log(add(1, 2));

2. 匯入預設匯出

// 名稱可以隨意取 (不推薦)
import MyCalc from './calculator';

3. 一次匯入所有內容

import * as MathUtils from './math';

console.log(MathUtils.PI);
console.log(MathUtils.add(1, 2));

Type-Only Imports (僅型別匯入)

這是 TypeScript 特有的語法。如果你匯入的東西只是一個 Type 或 Interface,建議加上 type 關鍵字。

// 清楚表明:我只借用它的「型別」,不會產生任何 JS 程式碼
import type { User } from './user';

const u: User = { name: 'Alice' };

這樣做的好處是:

  1. 編譯優化:Babel 或其他轉譯器可以放心地直接刪除這行 import,不用擔心會有副作用。
  2. 避免循環依賴:有時候兩個檔案互相引用型別,用 import type 可以解決某些循環引用的問題。

常見問題

Q: 以前的 require() 呢?

TypeScript 當然支援 CommonJS 的 require(),但在 .ts 檔案中,除非你在寫非常舊的 Node.js 腳本,否則請全面使用 import。TypeScript 編譯器會幫你把它轉成適合的 runtime 格式 (CommonJS or ESM)。

Q: namespace 還能用嗎?

技術上可以,但不建議。現在的 namespace 主要只用於 .d.ts 定義檔中,用來描述那些舊的 JavaScript 函式庫結構。在一般應用程式開發中,請使用 ES Modules。