5.1 模块系统概述

5.1.1 模块的概念

TypeScript 支持现代 JavaScript 的模块系统,包括 ES6 模块、CommonJS、AMD 等格式。模块是独立的代码单元,具有自己的作用域。

// 模块的基本特征
// 1. 顶级的 import 或 export 声明
// 2. 独立的作用域
// 3. 可以导入和导出功能

// math.ts - 一个简单的模块
export function add(a: number, b: number): number {
    return a + b;
}

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

export const PI = 3.14159;

// 默认导出
export default function multiply(a: number, b: number): number {
    return a * b;
}

5.1.2 模块解析策略

TypeScript 支持两种模块解析策略:

// tsconfig.json 中的模块解析配置
{
    "compilerOptions": {
        "moduleResolution": "node", // 或 "classic"
        "baseUrl": "./src",
        "paths": {
            "@/*": ["*"],
            "@utils/*": ["utils/*"],
            "@components/*": ["components/*"]
        }
    }
}

// 使用路径映射
import { Button } from '@components/Button';
import { formatDate } from '@utils/date';

5.2 导入和导出

5.2.1 命名导出和导入

// utils.ts - 命名导出
export function formatCurrency(amount: number): string {
    return `$${amount.toFixed(2)}`;
}

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

export interface User {
    id: string;
    name: string;
    email: string;
}

export class UserService {
    private users: User[] = [];
    
    addUser(user: User): void {
        this.users.push(user);
    }
    
    getUser(id: string): User | undefined {
        return this.users.find(u => u.id === id);
    }
}

// 批量导出
export { formatCurrency, formatDate, User, UserService };
// main.ts - 命名导入
import { formatCurrency, formatDate } from './utils';
import { User, UserService } from './utils';

// 使用别名
import { formatCurrency as currency } from './utils';

// 导入所有命名导出
import * as Utils from './utils';

// 使用导入的功能
const price = formatCurrency(99.99);
const today = formatDate(new Date());

const user: User = {
    id: '1',
    name: 'Alice',
    email: 'alice@example.com'
};

const userService = new UserService();
userService.addUser(user);

// 使用命名空间式导入
const formattedPrice = Utils.formatCurrency(149.99);
const service = new Utils.UserService();

5.2.2 默认导出和导入

// logger.ts - 默认导出
class Logger {
    private prefix: string;
    
    constructor(prefix: string = '[LOG]') {
        this.prefix = prefix;
    }
    
    info(message: string): void {
        console.log(`${this.prefix} INFO: ${message}`);
    }
    
    error(message: string): void {
        console.error(`${this.prefix} ERROR: ${message}`);
    }
    
    warn(message: string): void {
        console.warn(`${this.prefix} WARN: ${message}`);
    }
}

export default Logger;

// 也可以这样写
// export { Logger as default };
// app.ts - 默认导入
import Logger from './logger';

// 可以使用任何名称
import MyLogger from './logger';
import CustomLogger from './logger';

// 使用默认导入
const logger = new Logger('[APP]');
logger.info('Application started');

// 混合导入(默认 + 命名)
import Logger, { formatCurrency, User } from './utils';

5.2.3 重新导出

// index.ts - 重新导出模块
// 重新导出所有命名导出
export * from './utils';
export * from './logger';

// 重新导出默认导出
export { default as Logger } from './logger';
export { default as ApiClient } from './api-client';

// 选择性重新导出
export { formatCurrency, User } from './utils';

// 重新导出并重命名
export { UserService as UserManager } from './utils';

// 创建统一的 API
export {
    // 工具函数
    formatCurrency,
    formatDate,
    
    // 类型
    User,
    
    // 类
    UserService,
    Logger
} from './internal-modules';

5.3 模块类型声明

5.3.1 声明文件基础

// types.d.ts - 类型声明文件
declare module 'my-library' {
    export interface Config {
        apiUrl: string;
        timeout: number;
    }
    
    export function initialize(config: Config): void;
    export function getData<T>(endpoint: string): Promise<T>;
    
    export default class MyLibrary {
        constructor(config: Config);
        request<T>(endpoint: string): Promise<T>;
    }
}

// 全局类型声明
declare global {
    interface Window {
        myGlobalFunction: (data: any) => void;
        APP_CONFIG: {
            version: string;
            environment: 'development' | 'production';
        };
    }
    
    namespace NodeJS {
        interface ProcessEnv {
            NODE_ENV: 'development' | 'production' | 'test';
            API_URL: string;
            DATABASE_URL: string;
        }
    }
}

// 模块扩展
declare module 'express' {
    interface Request {
        user?: {
            id: string;
            email: string;
        };
    }
}

5.3.2 环境模块声明

// global.d.ts
// 为没有类型声明的第三方库创建声明
declare module 'some-untyped-library' {
    const content: any;
    export default content;
}

// 为特定文件类型创建声明
declare module '*.css' {
    const content: { [className: string]: string };
    export default content;
}

declare module '*.png' {
    const content: string;
    export default content;
}

declare module '*.svg' {
    import React from 'react';
    const content: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
    export default content;
}

// JSON 模块
declare module '*.json' {
    const content: any;
    export default content;
}

5.3.3 创建自己的类型包

// package.json
{
    "name": "my-types-package",
    "version": "1.0.0",
    "types": "index.d.ts",
    "files": [
        "index.d.ts",
        "lib/"
    ]
}

// index.d.ts
export interface ApiResponse<T> {
    data: T;
    status: number;
    message: string;
    timestamp: string;
}

export interface PaginatedResponse<T> extends ApiResponse<T[]> {
    pagination: {
        page: number;
        limit: number;
        total: number;
        totalPages: number;
    };
}

export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';

export interface RequestConfig {
    method: HttpMethod;
    url: string;
    headers?: Record<string, string>;
    params?: Record<string, any>;
    data?: any;
}

// lib/api.d.ts
export declare class ApiClient {
    constructor(baseURL: string, defaultHeaders?: Record<string, string>);
    
    request<T>(config: RequestConfig): Promise<ApiResponse<T>>;
    get<T>(url: string, params?: Record<string, any>): Promise<ApiResponse<T>>;
    post<T>(url: string, data?: any): Promise<ApiResponse<T>>;
    put<T>(url: string, data?: any): Promise<ApiResponse<T>>;
    delete<T>(url: string): Promise<ApiResponse<T>>;
}

5.4 命名空间

5.4.1 命名空间基础

虽然模块是组织代码的推荐方式,但命名空间在某些场景下仍然有用:

// geometry.ts - 使用命名空间组织相关功能
namespace Geometry {
    export interface Point {
        x: number;
        y: number;
    }
    
    export interface Rectangle {
        topLeft: Point;
        width: number;
        height: number;
    }
    
    export namespace Circle {
        export interface CircleType {
            center: Point;
            radius: number;
        }
        
        export function area(circle: CircleType): number {
            return Math.PI * circle.radius * circle.radius;
        }
        
        export function circumference(circle: CircleType): number {
            return 2 * Math.PI * circle.radius;
        }
    }
    
    export namespace Rectangle {
        export function area(rect: Rectangle): number {
            return rect.width * rect.height;
        }
        
        export function perimeter(rect: Rectangle): number {
            return 2 * (rect.width + rect.height);
        }
        
        export function contains(rect: Rectangle, point: Point): boolean {
            return point.x >= rect.topLeft.x &&
                   point.x <= rect.topLeft.x + rect.width &&
                   point.y >= rect.topLeft.y &&
                   point.y <= rect.topLeft.y + rect.height;
        }
    }
    
    export function distance(p1: Point, p2: Point): number {
        const dx = p2.x - p1.x;
        const dy = p2.y - p1.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}

// 使用命名空间
const point1: Geometry.Point = { x: 0, y: 0 };
const point2: Geometry.Point = { x: 3, y: 4 };

const circle: Geometry.Circle.CircleType = {
    center: point1,
    radius: 5
};

const rect: Geometry.Rectangle = {
    topLeft: { x: 1, y: 1 },
    width: 10,
    height: 8
};

console.log('Distance:', Geometry.distance(point1, point2));
console.log('Circle area:', Geometry.Circle.area(circle));
console.log('Rectangle area:', Geometry.Rectangle.area(rect));
console.log('Point in rectangle:', Geometry.Rectangle.contains(rect, point2));

5.4.2 命名空间合并

// 命名空间可以跨文件合并
// file1.ts
namespace Utils {
    export function formatDate(date: Date): string {
        return date.toLocaleDateString();
    }
}

// file2.ts
namespace Utils {
    export function formatCurrency(amount: number): string {
        return `$${amount.toFixed(2)}`;
    }
}

// file3.ts
namespace Utils {
    export interface Config {
        dateFormat: string;
        currency: string;
    }
    
    export const defaultConfig: Config = {
        dateFormat: 'MM/DD/YYYY',
        currency: 'USD'
    };
}

// 使用合并后的命名空间
// main.ts
/// <reference path="file1.ts" />
/// <reference path="file2.ts" />
/// <reference path="file3.ts" />

// 现在 Utils 命名空间包含所有导出的成员
const config = Utils.defaultConfig;
const formattedDate = Utils.formatDate(new Date());
const formattedPrice = Utils.formatCurrency(99.99);

5.4.3 命名空间与模块的结合

// api.ts - 在模块中使用命名空间
export namespace API {
    export interface RequestConfig {
        method: string;
        url: string;
        headers?: Record<string, string>;
        data?: any;
    }
    
    export interface Response<T> {
        data: T;
        status: number;
        statusText: string;
    }
    
    export namespace HTTP {
        export const GET = 'GET';
        export const POST = 'POST';
        export const PUT = 'PUT';
        export const DELETE = 'DELETE';
    }
    
    export namespace Status {
        export const OK = 200;
        export const CREATED = 201;
        export const BAD_REQUEST = 400;
        export const UNAUTHORIZED = 401;
        export const NOT_FOUND = 404;
        export const INTERNAL_SERVER_ERROR = 500;
    }
    
    export class Client {
        private baseURL: string;
        
        constructor(baseURL: string) {
            this.baseURL = baseURL;
        }
        
        async request<T>(config: RequestConfig): Promise<Response<T>> {
            // 实现 HTTP 请求逻辑
            const response = await fetch(`${this.baseURL}${config.url}`, {
                method: config.method,
                headers: config.headers,
                body: config.data ? JSON.stringify(config.data) : undefined
            });
            
            const data = await response.json();
            
            return {
                data,
                status: response.status,
                statusText: response.statusText
            };
        }
        
        async get<T>(url: string): Promise<Response<T>> {
            return this.request<T>({
                method: HTTP.GET,
                url
            });
        }
        
        async post<T>(url: string, data: any): Promise<Response<T>> {
            return this.request<T>({
                method: HTTP.POST,
                url,
                data
            });
        }
    }
}

// 使用
import { API } from './api';

const client = new API.Client('https://api.example.com');

client.get<{ users: any[] }>('/users')
    .then(response => {
        if (response.status === API.Status.OK) {
            console.log('Users:', response.data.users);
        }
    });

5.5 模块加载和打包

5.5.1 不同的模块格式

// tsconfig.json - 配置不同的模块格式
{
    "compilerOptions": {
        "module": "ES2020",        // ES6 模块
        // "module": "CommonJS",   // Node.js 风格
        // "module": "AMD",        // RequireJS
        // "module": "UMD",        // 通用模块定义
        // "module": "System",     // SystemJS
        
        "target": "ES2020",
        "moduleResolution": "node",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true
    }
}

// 条件导入(动态导入)
async function loadModule() {
    if (process.env.NODE_ENV === 'development') {
        const { DevLogger } = await import('./dev-logger');
        return new DevLogger();
    } else {
        const { ProdLogger } = await import('./prod-logger');
        return new ProdLogger();
    }
}

// 代码分割
const LazyComponent = React.lazy(() => import('./LazyComponent'));

// 动态导入类型
type LazyType = typeof import('./types').SomeType;

5.5.2 模块解析配置

// tsconfig.json - 高级模块解析配置
{
    "compilerOptions": {
        "baseUrl": "./src",
        "paths": {
            // 路径映射
            "@/*": ["*"],
            "@components/*": ["components/*"],
            "@utils/*": ["utils/*"],
            "@services/*": ["services/*"],
            "@types/*": ["types/*"],
            
            // 库别名
            "lodash": ["node_modules/lodash-es"],
            "react": ["node_modules/@types/react"]
        },
        
        // 类型根目录
        "typeRoots": [
            "./node_modules/@types",
            "./src/types"
        ],
        
        // 包含的类型库
        "types": [
            "node",
            "jest",
            "react"
        ],
        
        // 模块解析策略
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true
    }
}

// 使用路径映射
import { Button } from '@components/Button';
import { formatDate } from '@utils/date';
import { UserService } from '@services/user';
import { User } from '@types/user';

// 导入 JSON
import config from './config.json';

5.5.3 模块联邦和微前端

// webpack.config.js - 模块联邦配置
const ModuleFederationPlugin = require('@module-federation/webpack');

module.exports = {
    plugins: [
        new ModuleFederationPlugin({
            name: 'shell',
            remotes: {
                mfe1: 'mfe1@http://localhost:3001/remoteEntry.js',
                mfe2: 'mfe2@http://localhost:3002/remoteEntry.js'
            },
            shared: {
                react: { singleton: true },
                'react-dom': { singleton: true }
            }
        })
    ]
};

// types/module-federation.d.ts
declare module 'mfe1/Component' {
    const Component: React.ComponentType<any>;
    export default Component;
}

declare module 'mfe2/Service' {
    export interface ServiceType {
        getData(): Promise<any>;
    }
    
    const Service: ServiceType;
    export default Service;
}

// 使用远程模块
import React, { Suspense } from 'react';

const RemoteComponent = React.lazy(() => import('mfe1/Component'));

function App() {
    return (
        <div>
            <h1>Shell Application</h1>
            <Suspense fallback={<div>Loading...</div>}>
                <RemoteComponent />
            </Suspense>
        </div>
    );
}

5.6 实际应用案例

5.6.1 构建可扩展的插件系统

// plugin-system.ts
export interface Plugin {
    name: string;
    version: string;
    initialize(): void;
    destroy(): void;
}

export interface PluginManager {
    register(plugin: Plugin): void;
    unregister(pluginName: string): void;
    getPlugin(name: string): Plugin | undefined;
    getAllPlugins(): Plugin[];
}

export class DefaultPluginManager implements PluginManager {
    private plugins = new Map<string, Plugin>();
    
    register(plugin: Plugin): void {
        if (this.plugins.has(plugin.name)) {
            throw new Error(`Plugin ${plugin.name} is already registered`);
        }
        
        this.plugins.set(plugin.name, plugin);
        plugin.initialize();
    }
    
    unregister(pluginName: string): void {
        const plugin = this.plugins.get(pluginName);
        if (plugin) {
            plugin.destroy();
            this.plugins.delete(pluginName);
        }
    }
    
    getPlugin(name: string): Plugin | undefined {
        return this.plugins.get(name);
    }
    
    getAllPlugins(): Plugin[] {
        return Array.from(this.plugins.values());
    }
}

// 插件基类
export abstract class BasePlugin implements Plugin {
    abstract name: string;
    abstract version: string;
    
    initialize(): void {
        console.log(`Plugin ${this.name} v${this.version} initialized`);
    }
    
    destroy(): void {
        console.log(`Plugin ${this.name} destroyed`);
    }
}
// plugins/logger-plugin.ts
import { BasePlugin } from '../plugin-system';

export class LoggerPlugin extends BasePlugin {
    name = 'logger';
    version = '1.0.0';
    
    private logLevel: 'debug' | 'info' | 'warn' | 'error' = 'info';
    
    initialize(): void {
        super.initialize();
        this.setupGlobalLogger();
    }
    
    private setupGlobalLogger(): void {
        (window as any).logger = {
            debug: (msg: string) => this.log('debug', msg),
            info: (msg: string) => this.log('info', msg),
            warn: (msg: string) => this.log('warn', msg),
            error: (msg: string) => this.log('error', msg)
        };
    }
    
    private log(level: string, message: string): void {
        if (this.shouldLog(level)) {
            console.log(`[${level.toUpperCase()}] ${message}`);
        }
    }
    
    private shouldLog(level: string): boolean {
        const levels = ['debug', 'info', 'warn', 'error'];
        return levels.indexOf(level) >= levels.indexOf(this.logLevel);
    }
    
    setLogLevel(level: 'debug' | 'info' | 'warn' | 'error'): void {
        this.logLevel = level;
    }
}
// app.ts
import { DefaultPluginManager } from './plugin-system';
import { LoggerPlugin } from './plugins/logger-plugin';

class Application {
    private pluginManager = new DefaultPluginManager();
    
    async start(): Promise<void> {
        // 注册核心插件
        this.pluginManager.register(new LoggerPlugin());
        
        // 动态加载插件
        await this.loadPlugins();
        
        console.log('Application started with plugins:', 
            this.pluginManager.getAllPlugins().map(p => p.name));
    }
    
    private async loadPlugins(): Promise<void> {
        const pluginConfigs = [
            { name: 'analytics', path: './plugins/analytics-plugin' },
            { name: 'theme', path: './plugins/theme-plugin' }
        ];
        
        for (const config of pluginConfigs) {
            try {
                const module = await import(config.path);
                const PluginClass = module.default || module[config.name + 'Plugin'];
                this.pluginManager.register(new PluginClass());
            } catch (error) {
                console.warn(`Failed to load plugin ${config.name}:`, error);
            }
        }
    }
}

const app = new Application();
app.start();

5.6.2 构建类型安全的配置系统

// config-system.ts
export interface ConfigSchema {
    [key: string]: {
        type: 'string' | 'number' | 'boolean' | 'object' | 'array';
        required?: boolean;
        default?: any;
        validate?: (value: any) => boolean;
        description?: string;
    };
}

export type ConfigFromSchema<T extends ConfigSchema> = {
    [K in keyof T]: T[K]['required'] extends true
        ? T[K]['type'] extends 'string'
            ? string
            : T[K]['type'] extends 'number'
            ? number
            : T[K]['type'] extends 'boolean'
            ? boolean
            : any
        : T[K]['type'] extends 'string'
        ? string | undefined
        : T[K]['type'] extends 'number'
        ? number | undefined
        : T[K]['type'] extends 'boolean'
        ? boolean | undefined
        : any;
};

export class ConfigManager<T extends ConfigSchema> {
    private config: Partial<ConfigFromSchema<T>> = {};
    
    constructor(private schema: T) {}
    
    load(source: Partial<ConfigFromSchema<T>>): void {
        this.config = { ...this.getDefaults(), ...source };
        this.validate();
    }
    
    get<K extends keyof T>(key: K): ConfigFromSchema<T>[K] {
        return this.config[key] as ConfigFromSchema<T>[K];
    }
    
    set<K extends keyof T>(key: K, value: ConfigFromSchema<T>[K]): void {
        this.config[key] = value;
        this.validateKey(key);
    }
    
    private getDefaults(): Partial<ConfigFromSchema<T>> {
        const defaults: any = {};
        for (const [key, schema] of Object.entries(this.schema)) {
            if (schema.default !== undefined) {
                defaults[key] = schema.default;
            }
        }
        return defaults;
    }
    
    private validate(): void {
        for (const key of Object.keys(this.schema)) {
            this.validateKey(key as keyof T);
        }
    }
    
    private validateKey<K extends keyof T>(key: K): void {
        const schema = this.schema[key];
        const value = this.config[key];
        
        if (schema.required && value === undefined) {
            throw new Error(`Required config key '${String(key)}' is missing`);
        }
        
        if (value !== undefined) {
            if (schema.validate && !schema.validate(value)) {
                throw new Error(`Invalid value for config key '${String(key)}'`);
            }
        }
    }
}
// app-config.ts
import { ConfigManager, ConfigSchema } from './config-system';

// 定义应用配置模式
const appConfigSchema = {
    apiUrl: {
        type: 'string' as const,
        required: true,
        validate: (value: string) => value.startsWith('http'),
        description: 'API base URL'
    },
    timeout: {
        type: 'number' as const,
        default: 5000,
        validate: (value: number) => value > 0,
        description: 'Request timeout in milliseconds'
    },
    debug: {
        type: 'boolean' as const,
        default: false,
        description: 'Enable debug mode'
    },
    features: {
        type: 'object' as const,
        default: {},
        description: 'Feature flags'
    }
} satisfies ConfigSchema;

// 创建类型安全的配置管理器
export const appConfig = new ConfigManager(appConfigSchema);

// 加载配置
appConfig.load({
    apiUrl: 'https://api.example.com',
    timeout: 10000,
    debug: true,
    features: {
        newUI: true,
        analytics: false
    }
});

// 类型安全的配置访问
const apiUrl: string = appConfig.get('apiUrl'); // 类型为 string
const timeout: number | undefined = appConfig.get('timeout'); // 类型为 number | undefined
const debug: boolean | undefined = appConfig.get('debug'); // 类型为 boolean | undefined

5.7 本章练习

练习 1:模块重构

将以下单文件代码重构为模块化结构:

// 原始代码(单文件)
interface User {
    id: string;
    name: string;
    email: string;
}

class UserService {
    private users: User[] = [];
    
    addUser(user: User): void {
        this.users.push(user);
    }
    
    getUser(id: string): User | undefined {
        return this.users.find(u => u.id === id);
    }
}

function validateEmail(email: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function formatUserName(user: User): string {
    return user.name.toUpperCase();
}

const userService = new UserService();
const user: User = {
    id: '1',
    name: 'Alice',
    email: 'alice@example.com'
};

if (validateEmail(user.email)) {
    userService.addUser(user);
    console.log(formatUserName(user));
}

// 任务:将上述代码重构为以下模块结构:
// - types/user.ts
// - services/user-service.ts
// - utils/validation.ts
// - utils/formatting.ts
// - index.ts

练习 2:创建插件架构

设计并实现一个简单的插件系统:

// 要求:
// 1. 定义插件接口
// 2. 实现插件管理器
// 3. 创建至少两个示例插件
// 4. 支持插件的动态加载和卸载
// 5. 提供插件间通信机制

// 提示:考虑以下接口设计
interface Plugin {
    name: string;
    version: string;
    dependencies?: string[];
    
    initialize(context: PluginContext): Promise<void>;
    destroy(): Promise<void>;
}

interface PluginContext {
    emit(event: string, data?: any): void;
    on(event: string, handler: (data?: any) => void): void;
    getPlugin(name: string): Plugin | undefined;
}

练习 3:类型安全的国际化系统

实现一个类型安全的国际化(i18n)系统:

// 要求:
// 1. 支持嵌套的翻译键
// 2. 支持参数插值
// 3. 提供类型安全的翻译函数
// 4. 支持多语言切换
// 5. 支持延迟加载语言包

// 示例翻译文件结构:
// locales/
//   en.json
//   zh.json
//   fr.json

// 使用示例:
// const t = useTranslation();
// t('common.buttons.save'); // 类型安全
// t('messages.welcome', { name: 'Alice' }); // 支持参数

5.8 本章总结

本章深入探讨了 TypeScript 的模块系统和命名空间,包括:

  1. 模块系统概述:了解了模块的概念和解析策略
  2. 导入和导出:掌握了各种导入导出语法和最佳实践
  3. 模块类型声明:学习了如何为模块创建类型声明
  4. 命名空间:了解了命名空间的使用场景和合并机制
  5. 模块加载和打包:掌握了不同模块格式和高级配置
  6. 实际应用案例:通过插件系统和配置系统学习了模块化设计

模块系统是构建大型 TypeScript 应用的基础,正确使用模块化可以提高代码的可维护性、可测试性和可重用性。命名空间虽然不如模块常用,但在特定场景下仍然有其价值。

下一章我们将学习异步编程,包括 Promise、async/await 和错误处理等内容。