本文 AI 產出,尚未審核

TypeScript 模組與命名空間 – default export / named export

簡介

在大型前端或 Node.js 專案中,模組化 是維持程式碼可讀、可維護與可重用的基礎。TypeScript 繼承了 ES6 的模組語法,提供 default exportnamed export 兩種不同的匯出方式。掌握它們的差異與使用時機,能讓你在團隊協作、程式碼分割以及測試上得到更好的體驗。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶領讀者了解 default exportnamed export 在 TypeScript 中的使用方式,並提供實務上的應用情境,幫助你在日常開發中快速上手。


核心概念

1. 什麼是 export?

在 ES6(亦即 ES2015)之前,JavaScript 並沒有正式的模組系統,開發者只能靠全域變數或 IIFE(Immediately‑Invoked Function Expression)來模擬。ES6 引入 exportimport,讓每個檔案(module)都能明確宣告 對外提供 的 API。

// utils.ts
export function add(a: number, b: number): number {
  return a + b;
}

其他檔案只要 import { add } from "./utils" 即可使用。

2. default export

default export 代表唯一的預設匯出,一個模組只能有一個 default。匯入時不需要使用大括號,且可以自行命名。

// logger.ts
export default function log(message: string): void {
  console.log(`[Log] ${message}`);
}
// app.ts
import logger from "./logger";   // 直接使用自訂的名稱
logger("Hello TypeScript!");

為何使用 default?

  • 單一職責:模組只提供一個主要功能(例如 React 元件、服務類別)。
  • 簡化匯入語法:使用者不必記得匯出的名稱,只要給予自己慣用的變數名即可。

3. named export

named export 允許在同一個檔案中匯出多個變數、函式或類別。匯入時必須使用大括號,且名稱必須與匯出時一致(除非使用 as 重新命名)。

// math.ts
export const PI = 3.1415;
export function multiply(a: number, b: number): number {
  return a * b;
}
export class Calculator {
  static square(x: number) {
    return x * x;
  }
}
// main.ts
import { PI, multiply, Calculator } from "./math";
console.log(PI);                     // 3.1415
console.log(multiply(2, 3));         // 6
console.log(Calculator.square(5));  // 25

為何使用 named?

  • 多元匯出:一個模組可能同時提供工具函式、常數、類別等。
  • 靜態分析友好:IDE 能自動提示可匯入的成員,減少拼寫錯誤。

4. 混用 default 與 named

雖然可以同時存在,但要注意匯入語法的差異。

// service.ts
export default class ApiService {
  get() { /* ... */ }
}
export const API_ENDPOINT = "https://api.example.com";
export type User = { id: number; name: string };
// consumer.ts
import ApiService, { API_ENDPOINT, User } from "./service";

const api = new ApiService();
console.log(API_ENDPOINT);

5. 重新匯出(re‑export)

在大型專案中,常會建立「聚合模組」來統一匯出子模組的成員。

// index.ts
export { default as Logger } from "./logger";
export * from "./math";

使用者只需要一次 import { Logger, PI } from "./index" 即可取得所有功能。


程式碼範例

範例 1:React 元件的 default export

// Button.tsx
import React from "react";

type Props = {
  label: string;
  onClick: () => void;
};

export default function Button({ label, onClick }: Props) {
  return <button onClick={onClick}>{label}</button>;
}
// App.tsx
import React from "react";
import Button from "./Button";   // 直接匯入 default

export default function App() {
  return (
    <div>
      <Button label="點我" onClick={() => alert("Hello")} />
    </div>
  );
}

重點:React 元件通常使用 default export,因為每個檔案只會有一個主要的 UI 組件。

範例 2:工具函式的 named export

// string-utils.ts
/** 取得字串長度 */
export function length(str: string): number {
  return str.length;
}

/** 反轉字串 */
export function reverse(str: string): string {
  return str.split("").reverse().join("");
}
// demo.ts
import { length, reverse } from "./string-utils";

console.log(length("TypeScript"));   // 10
console.log(reverse("abc"));         // "cba"

技巧:如果你只需要其中一個函式,可以使用 tree‑shaking,只打包被使用的部份。

範例 3:同時使用 default 與 named

// db.ts
export default class Database {
  connect() { console.log("connected"); }
}
export const DB_VERSION = "1.0.0";
export type Config = {
  host: string;
  port: number;
};
// index.ts
import Database, { DB_VERSION, Config } from "./db";

const db = new Database();
db.connect();
console.log(`Version: ${DB_VERSION}`);

const cfg: Config = { host: "localhost", port: 5432 };

範例 4:聚合模組(barrel)與重新匯出

// utils/math.ts
export function add(a: number, b: number): number {
  return a + b;
}
export function sub(a: number, b: number): number {
  return a - b;
}
// utils/string.ts
export function upper(s: string): string {
  return s.toUpperCase();
}
export function lower(s: string): string {
  return s.toLowerCase();
}
// utils/index.ts   // ← barrel
export * from "./math";
export * from "./string";
// app.ts
import { add, upper } from "./utils";

console.log(add(5, 7));   // 12
console.log(upper("ts")); // "TS"

範例 5:動態匯入(lazy load)與 default export

// heavy-module.ts
export default function heavyTask() {
  console.log("執行大型運算...");
}
// main.ts
async function run() {
  const { default: heavyTask } = await import("./heavy-module");
  heavyTask();   // 只有在需要時才載入
}
run();

實務意義:在前端 SPA 中,利用 import() 搭配 default export 可實現 程式碼分割,減少首次載入體積。


常見陷阱與最佳實踐

陷阱 說明 建議解決方式
同時使用 default 與 named,匯入時忘記大括號 import foo from "./mod" 只能取得 default,若想同時取得 named 必須加大括號。 範例import foo, { bar } from "./mod"
命名衝突 匯入多個模組的 named 成員時,名稱相同會產生衝突。 使用 as 重新命名:import { foo as fooA } from "./a"
過度依賴 default,失去自文件的語意 大量 default 會讓 IDE 難以自動補全,且不易閱讀。 若模組提供多功能,優先使用 named export;僅有單一主體時才使用 default。
未設定 esModuleInterop,導入 CommonJS 模組時出錯 TypeScript 需要 esModuleInterop: true 才能正確處理 module.exports = ... tsconfig.json 中開啟 esModuleInterop,或使用 import * as foo from "cjs-lib"
忘記在 barrel 中重新匯出 default export * from "./module" 不會重新匯出 default 成員。 必須額外寫 export { default as Xxx } from "./module"

最佳實踐

  1. 遵循單一職責原則:每個檔案只提供一個「核心」功能時,使用 default export;功能多元時使用 named export
  2. 保持匯入一致性:團隊內部約定匯入風格(例如全部使用大括號),避免混雜。
  3. 利用 TypeScript 型別export typeexport interface 只會在編譯時出現,不會產生 runtime 負擔。
  4. 結合 lint 規則:使用 eslint-plugin-importtypescript-eslint 來檢查未使用的匯入、重複匯入等問題。
  5. 避免過度的 re‑export:Barrel 便利但過度使用會讓依賴圖變得不透明,適度即可。

實際應用場景

場景 建議匯出方式 為什麼
React 元件庫 每個元件使用 default export,再在 index.ts 中聚合 export { default as Button } from "./Button" 使用者可一次性匯入 import { Button, Modal } from "my-ui",同時保留元件的單一預設匯出。
工具函式集合 (lodashdate-fns) named export 為主 呼叫者只需要其中幾個函式,可透過 tree‑shaking 減少 bundle 大小。
服務層 (API client) default export 服務類別 + named export 常數、型別 類別作為主要入口,常數與型別供其他模組使用。
多語言資源檔 named export 每個語系物件 讓使用者自行挑選語系:import { en, zhTW } from "./i18n"
動態載入的插件系統 每個插件使用 default export,外層用 import() 動態載入 讓插件只在需要時載入,減少初始載入時間。

總結

  • default export 適合「單一核心」的模組,如 React 元件、服務類別或大型功能的入口。匯入時可自行命名,語法簡潔。
  • named export 則是「多元功能」的最佳選擇,支援同時匯出多個函式、常數、類別與型別,對 IDE 與 tree‑shaking 更友好。
  • 兩者可以 混合使用,但必須注意匯入語法的差異與 export * 不會重新匯出 default
  • 在實務開發中,聚合模組(barrel)動態匯入、以及 型別匯出 都是提升可維護性與效能的利器。
  • 避免常見陷阱(命名衝突、忘記大括號、未開 esModuleInterop)並遵循最佳實踐,能讓你的 TypeScript 專案更乾淨、可擴充。

掌握了 default 與 named export 的使用技巧後,你就能在 模組設計程式碼分割、以及 團隊協作 上游刃有餘,寫出結構清晰、效能卓越的 TypeScript 應用程式。祝開發順利 🚀