TypeScript 命名空間 (Namespaces)

命名空間 (Namespaces) 是 TypeScript 提供的一種組織程式碼的方式,用來將相關的程式碼分組在一起,避免全域命名衝突。

在現代 TypeScript 開發中,通常建議使用 ES 模組 (import/export) 而非命名空間。命名空間主要用於組織大型專案中的型別定義,或是在沒有模組系統的環境中使用。

基本語法

使用 namespace 關鍵字定義命名空間:

namespace Geometry {
  export const PI = 3.14159;

  export function circleArea(radius: number): number {
    return PI * radius * radius;
  }

  export function rectangleArea(width: number, height: number): number {
    return width * height;
  }

  // 未 export 的成員只能在命名空間內部存取
  function internalHelper(): void {
    console.log('This is internal');
  }
}

// 使用命名空間中的成員
console.log(Geometry.PI);
console.log(Geometry.circleArea(5));
// Geometry.internalHelper();  // 錯誤:未匯出

巢狀命名空間

namespace Company {
  export namespace Departments {
    export namespace Engineering {
      export function getTeamSize(): number {
        return 50;
      }
    }

    export namespace Marketing {
      export function getBudget(): number {
        return 100000;
      }
    }
  }

  export function getInfo(): string {
    return 'Company Inc.';
  }
}

console.log(Company.Departments.Engineering.getTeamSize());
console.log(Company.Departments.Marketing.getBudget());

命名空間別名

使用 import 為巢狀命名空間建立別名:

namespace Shapes {
  export namespace Polygons {
    export class Triangle {
      constructor(
        public base: number,
        public height: number
      ) {}

      getArea(): number {
        return (this.base * this.height) / 2;
      }
    }

    export class Rectangle {
      constructor(
        public width: number,
        public height: number
      ) {}

      getArea(): number {
        return this.width * this.height;
      }
    }
  }
}

// 建立別名
import Polygons = Shapes.Polygons;

const triangle = new Polygons.Triangle(10, 5);
console.log(triangle.getArea());

跨檔案命名空間

命名空間可以跨多個檔案,編譯時會合併:

// shapes.ts
namespace Shapes {
  export class Circle {
    constructor(public radius: number) {}

    getArea(): number {
      return Math.PI * this.radius ** 2;
    }
  }
}

// more-shapes.ts
/// <reference path="shapes.ts" />
namespace Shapes {
  export class Square {
    constructor(public side: number) {}

    getArea(): number {
      return this.side ** 2;
    }
  }
}

// 使用時,Shapes 包含 Circle 和 Square
const circle = new Shapes.Circle(5);
const square = new Shapes.Square(4);

使用 tsconfig.json 合併

{
  "compilerOptions": {
    "outFile": "./dist/bundle.js"
  },
  "files": ["shapes.ts", "more-shapes.ts"]
}

命名空間與介面

namespace Validation {
  export interface Validator {
    isValid(value: string): boolean;
  }

  export class EmailValidator implements Validator {
    isValid(value: string): boolean {
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
    }
  }

  export class PhoneValidator implements Validator {
    isValid(value: string): boolean {
      return /^\d{10}$/.test(value);
    }
  }
}

const emailValidator = new Validation.EmailValidator();
console.log(emailValidator.isValid('test@example.com')); // true

const phoneValidator = new Validation.PhoneValidator();
console.log(phoneValidator.isValid('1234567890')); // true

命名空間與模組的差異

特性命名空間ES 模組
語法namespaceimport/export
載入方式編譯時合併執行時載入
封裝方式使用 export預設私有,使用 export 公開
檔案關係可跨檔案同名每個檔案是獨立模組
現代開發較少使用推薦使用
樹搖 (Tree Shaking)不支援支援

何時使用命名空間

1. 組織型別定義

// types/api.ts
namespace API {
  export namespace Response {
    export interface User {
      id: number;
      name: string;
    }

    export interface Post {
      id: number;
      title: string;
      content: string;
    }
  }

  export namespace Request {
    export interface CreateUser {
      name: string;
      email: string;
    }

    export interface UpdateUser {
      name?: string;
      email?: string;
    }
  }
}

// 使用
function getUser(): API.Response.User {
  return { id: 1, name: '小明' };
}

function createUser(data: API.Request.CreateUser): void {
  // ...
}

2. 擴充第三方函式庫

// 擴充 jQuery 的型別
declare namespace JQuery {
  interface JQuery {
    customPlugin(): JQuery;
  }
}

3. 舊版程式碼相容

// 維護舊的程式碼庫
namespace LegacyApp {
  export function initialize(): void {
    console.log('Legacy app initialized');
  }

  export namespace Utils {
    export function formatDate(date: Date): string {
      return date.toLocaleDateString();
    }
  }
}

全域擴充

使用命名空間擴充全域物件:

// 擴充 Window 物件
declare global {
  interface Window {
    myApp: {
      version: string;
      init(): void;
    };
  }
}

window.myApp = {
  version: '1.0.0',
  init() {
    console.log('App initialized');
  },
};

export {}; // 確保這是一個模組

命名空間與類別

命名空間可以與同名的類別合併:

class Album {
  label: Album.AlbumLabel;

  constructor(label: Album.AlbumLabel) {
    this.label = label;
  }
}

namespace Album {
  export interface AlbumLabel {
    name: string;
    year: number;
  }

  export function createLabel(name: string, year: number): AlbumLabel {
    return { name, year };
  }
}

const label = Album.createLabel('Sony', 2024);
const album = new Album(label);

命名空間與列舉

enum Color {
  Red,
  Green,
  Blue,
}

namespace Color {
  export function mixColors(c1: Color, c2: Color): Color {
    // 簡化的混色邏輯
    return Color.Green;
  }

  export function toHex(color: Color): string {
    switch (color) {
      case Color.Red:
        return '#FF0000';
      case Color.Green:
        return '#00FF00';
      case Color.Blue:
        return '#0000FF';
    }
  }
}

console.log(Color.toHex(Color.Red)); // #FF0000

從命名空間遷移到模組

如果你正在維護使用命名空間的舊程式碼,以下是遷移建議:

// 之前:命名空間
namespace Utils {
  export function formatDate(date: Date): string {
    return date.toLocaleDateString();
  }

  export function formatNumber(num: number): string {
    return num.toLocaleString();
  }
}

// 之後:ES 模組
// utils/date.ts
export function formatDate(date: Date): string {
  return date.toLocaleDateString();
}

// utils/number.ts
export function formatNumber(num: number): string {
  return num.toLocaleString();
}

// utils/index.ts
export * from './date';
export * from './number';

總結

  • 命名空間適合組織型別定義和維護舊程式碼
  • 新專案建議使用 ES 模組 (import/export)
  • 命名空間無法進行樹搖最佳化
  • 命名空間可以跨檔案合併
  • 命名空間可以與類別、列舉合併