TypeScript 模組 (Modules)

模組 (Modules) 是 TypeScript/JavaScript 中組織程式碼的方式。每個檔案都可以是一個獨立的模組,透過 importexport 來分享和使用程式碼。

匯出 (Export)

具名匯出 (Named Exports)

// math.ts
export const PI = 3.14159;

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

export function subtract(a: number, b: number): number {
  return a - b;
}

export interface Point {
  x: number;
  y: number;
}

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

也可以在檔案底部統一匯出:

// math.ts
const PI = 3.14159;

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

function subtract(a: number, b: number): number {
  return a - b;
}

export { PI, add, subtract };

預設匯出 (Default Export)

每個模組只能有一個預設匯出:

// user.ts
export default class User {
  constructor(public name: string) {}
}

// 或者函式
export default function greet(name: string): string {
  return `Hello, ${name}!`;
}

重新匯出 (Re-exports)

// index.ts - 將多個模組的匯出整合
export { add, subtract } from './math';
export { User } from './user';
export * from './utils'; // 匯出所有
export * as MathUtils from './math'; // 匯出為命名空間

匯入 (Import)

匯入具名匯出

// 匯入特定項目
import { add, subtract, PI } from './math';
console.log(add(1, 2));

// 重新命名
import { add as addNumbers } from './math';
console.log(addNumbers(1, 2));

// 匯入所有並使用命名空間
import * as Math from './math';
console.log(Math.add(1, 2));
console.log(Math.PI);

匯入預設匯出

// 預設匯出可以使用任何名稱
import User from './user';
import MyUser from './user'; // 相同,名稱可自訂

const user = new User('小明');

同時匯入預設和具名

import User, { UserRole, createUser } from './user';

只執行副作用

// 只執行模組程式碼,不匯入任何東西
import './polyfills';
import './styles.css';

型別匯入匯出

TypeScript 3.8+ 支援只匯入型別:

// types.ts
export interface User {
  id: number;
  name: string;
}

export type UserRole = 'admin' | 'user' | 'guest';

// 使用 type 關鍵字只匯入型別
import type { User, UserRole } from './types';

// 型別只在編譯時使用,不會出現在編譯後的 JavaScript 中
const user: User = { id: 1, name: '小明' };

內聯型別匯入

import { createUser, type User, type UserRole } from './user';

動態匯入 (Dynamic Imports)

使用 import() 函式在執行時動態載入模組:

async function loadModule() {
  const math = await import('./math');
  console.log(math.add(1, 2));
}

// 條件載入
async function loadFeature(featureName: string) {
  if (featureName === 'chart') {
    const { Chart } = await import('./chart');
    return new Chart();
  }
}

模組解析策略

tsconfig.json 中設定模組解析:

{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "node",
    "baseUrl": "./src",
    "paths": {
      "@/*": ["./*"],
      "@components/*": ["./components/*"]
    }
  }
}

路徑別名

設定後可以使用:

import { Button } from '@components/Button';
import { utils } from '@/utils';

全域模組擴增

擴增現有模組的型別:

// 擴增 Express 的 Request 型別
declare module 'express' {
  interface Request {
    user?: {
      id: string;
      name: string;
    };
  }
}

// 擴增全域物件
declare global {
  interface Window {
    myApp: {
      version: string;
    };
  }
}

CommonJS 互通

TypeScript 可以與 CommonJS 模組互通:

// tsconfig.json
{
    "compilerOptions": {
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true
    }
}

使用:

// 可以像 ES 模組一樣匯入 CommonJS 模組
import express from 'express';
import * as fs from 'fs';

模組結構最佳實踐

目錄結構

src/
├── index.ts          # 主入口
├── types/
│   └── index.ts      # 型別定義
├── utils/
│   ├── index.ts      # utils 入口
│   ├── string.ts
│   └── number.ts
├── services/
│   ├── index.ts
│   ├── user.service.ts
│   └── auth.service.ts
└── components/
    ├── index.ts
    ├── Button.tsx
    └── Input.tsx

Barrel 檔案 (index.ts)

// components/index.ts
export { Button } from './Button';
export { Input } from './Input';
export { Modal } from './Modal';

// 使用時可以簡化匯入
import { Button, Input, Modal } from './components';

實用範例

服務層模組

// services/user.service.ts
import type { User, CreateUserDTO } from '../types';

export class UserService {
  private users: User[] = [];

  async create(data: CreateUserDTO): Promise<User> {
    const user: User = {
      id: Date.now(),
      ...data,
    };
    this.users.push(user);
    return user;
  }

  async findById(id: number): Promise<User | undefined> {
    return this.users.find((u) => u.id === id);
  }
}

export const userService = new UserService();

工具函式模組

// utils/string.ts
export function capitalize(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

export function truncate(str: string, length: number): string {
  return str.length > length ? str.slice(0, length) + '...' : str;
}

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

常數模組

// constants/index.ts
export const API_BASE_URL = 'https://api.example.com';

export const HTTP_STATUS = {
  OK: 200,
  CREATED: 201,
  BAD_REQUEST: 400,
  NOT_FOUND: 404,
  INTERNAL_ERROR: 500,
} as const;

export type HttpStatus = (typeof HTTP_STATUS)[keyof typeof HTTP_STATUS];

設定模組

// config/index.ts
interface Config {
  apiUrl: string;
  timeout: number;
  debug: boolean;
}

const development: Config = {
  apiUrl: 'http://localhost:3000',
  timeout: 5000,
  debug: true,
};

const production: Config = {
  apiUrl: 'https://api.example.com',
  timeout: 10000,
  debug: false,
};

export const config = process.env.NODE_ENV === 'production' ? production : development;