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 的模块系统和命名空间,包括:
- 模块系统概述:了解了模块的概念和解析策略
- 导入和导出:掌握了各种导入导出语法和最佳实践
- 模块类型声明:学习了如何为模块创建类型声明
- 命名空间:了解了命名空间的使用场景和合并机制
- 模块加载和打包:掌握了不同模块格式和高级配置
- 实际应用案例:通过插件系统和配置系统学习了模块化设计
模块系统是构建大型 TypeScript 应用的基础,正确使用模块化可以提高代码的可维护性、可测试性和可重用性。命名空间虽然不如模块常用,但在特定场景下仍然有其价值。
下一章我们将学习异步编程,包括 Promise、async/await 和错误处理等内容。