TypeScript 模組 (Modules)
模組 (Modules) 是 TypeScript/JavaScript 中組織程式碼的方式。每個檔案都可以是一個獨立的模組,透過 import 和 export 來分享和使用程式碼。
匯出 (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;