模块化的演进
前端模块化发展历程
┌──────────────────────────────────────────────────────────────┐
│ 前端模块化演进 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. 远古时代 (2000前) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ // 全局变量,命名空间冲突 │ │
│ │ var App = {}; │ │
│ │ App.utils = { ... }; │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 2. IIFE 时代 (2006-2010) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ (function(global) { │ │
│ │ var privateVar = 1; │ │
│ │ global.Module = { ... }; │ │
│ │ })(window); │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. AMD/CMD 时代 (2010-2015) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ // RequireJS / SeaJS │ │
│ │ define('module', ['dep'], function(dep) { │ │
│ │ return { ... }; │ │
│ │ }); │ │
│ │ │ │
│ │ require(['module'], function(module) { ... }); │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 4. CommonJS 时代 (2010-2020) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ // Node.js 模块系统 │ │
│ │ const utils = require('./utils'); │ │
│ │ module.exports = { ... }; │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 5. ES Module 时代 (2015+) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ // 现代标准 │ │
│ │ import { utils } from './utils.js'; │ │
│ │ export const utils = { ... }; │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
ES Module 深入理解
基础语法
// named export
export const name = 'value';
export function foo() {}
// named import
import { name, foo } from './module.js';
// default export
export default function() {}
// default import
import myModule from './module.js';
// all exports
import * as all from './module.js';
// dynamic import
const module = await import('./module.js');
// re-export
export { foo, bar } from './other.js';
export { foo as alias } from './other.js';
// re-export all
export * from './other.js';
模块解析规则
// 相对导入(推荐)
import utils from './utils.js';
import helper from '../helpers.js';
import constants from '../../constants/index.js';
// 非相对导入
import react from 'react'; // 从 node_modules
// 别名导入
import { useState as useStateReact } from 'react';
模块作用域
// module-a.js
const privateVar = 'private'; // 模块内部作用域
export const publicVar = 'public'; // 可导入
// module-b.js
import { privateVar } from './module-a.js'; // Error! 不可访问
// 模块是单例
// module-a.js
export let count = 0;
export function increment() { count++; }
// module-b.js
import { count, increment } from './module-a.js';
increment();
console.log(count); // 1
// module-c.js
import { count } from './module-a.js';
console.log(count); // 1(同一个实例)
import() 动态导入
// 静态导入 - 模块立即加载
import _ from 'lodash'; // 整个 lodash 被打包
// 动态导入 - 按需加载(代码分割)
async function loadLodash() {
const _ = await import('lodash');
return _.default.range(10);
}
// React 中的使用
const LazyComponent = React.lazy(() => import('./HeavyComponent.jsx'));
// 条件导入
async function getFeature() {
if (featureEnabled) {
const { advancedFeature } = await import('./advanced.js');
return advancedFeature;
}
return null;
}
CSS 模块化
CSS Modules
CSS Modules 将 CSS 类名局部化,避免全局污染:
/* Button.module.css */
.button {
padding: 8px 16px;
border-radius: 4px;
}
.primary {
background: #007bff;
color: white;
}
.disabled {
opacity: 0.5;
pointer-events: none;
}
// Button.jsx
import styles from './Button.module.css';
export function Button({ variant = 'default', disabled, children }) {
const className = [
styles.button,
variant === 'primary' && styles.primary,
disabled && styles.disabled
].filter(Boolean).join(' ');
return (
<button className={className} disabled={disabled}>
{children}
</button>
);
}
编译后:
/* 生成的类名 */
.Button_button__xK7d3 { padding: 8px 16px; }
.Button_primary__rF9k2 { background: #007bff; }
.Button_disabled__mK9d3 { opacity: 0.5; }
CSS-in-JS
// Styled Components
import styled from 'styled-components';
const Button = styled.button`
padding: 8px 16px;
border-radius: 4px;
background: ${props => props.$primary ? '#007bff' : '#e0e0e0'};
color: ${props => props.$primary ? 'white' : 'black'};
`;
export function StyledButton({ $primary, children }) {
return <Button $primary={$primary}>{children}</Button>;
}
Tailwind CSS
Utility-first 的 CSS 框架:
// 使用预制类名
export function TailwindButton({ primary, children }) {
return (
<button className={`
px-4 py-2 rounded font-medium
${primary
? 'bg-blue-500 text-white hover:bg-blue-600'
: 'bg-gray-200 text-gray-800 hover:bg-gray-300'}
`}>
{children}
</button>
);
}
CSS 变量(Custom Properties)
:root {
--color-primary: #007bff;
--color-secondary: #6c757d;
--spacing-base: 8px;
--radius: 4px;
}
.button {
padding: var(--spacing-base) calc(var(--spacing-base) * 2);
border-radius: var(--radius);
background: var(--color-primary);
}
@media (prefers-color-scheme: dark) {
:root {
--color-primary: #0056b3;
--color-secondary: #adb5bd;
}
}
组件化架构
组件设计原则
┌─────────────────────────────────────────────────────────────┐
│ 组件设计原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 单一职责(SRP) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 一个组件只做一件事 │ │
│ │ │ │
│ │ ✅ Good: <UserCard /> 只显示用户信息 │ │
│ │ ❌ Bad: <UserCard /> 包含用户列表、详情弹窗 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 2. 开闭原则(OCP) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 对扩展开放,对修改关闭 │ │
│ │ │ │
│ │ ✅ Good: <Button variant="primary" /> │ │
│ │ <Button variant="secondary" /> │ │
│ │ ❌ Bad: if (variant === 'primary') { ... } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 3. 依赖倒置(DIP) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 依赖抽象,不依赖具体实现 │ │
│ │ │ │
│ │ ✅ Good: <Card title={title} onAction={handler} />│ │
│ │ ❌ Bad: <Card onConfirm={() => api.save()} /> │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
组件分类
┌─────────────────────────────────────────────────────────────┐
│ 组件分类体系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 展示组件 │ │ 容器组件 │ │
│ │ (Presentational)│ │ (Container) │ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ • 关注 UI 外观 │ │ • 关注逻辑 │ │
│ │ • 接收 props │ │ • 管理状态 │ │
│ │ • 无副作用 │ │ • 调用 API │ │
│ │ • 可复用 │ │ • 提供数据 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │ │
│ └──────────┬───────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 业务组件 │ │
│ │ (Business) │ │
│ ├─────────────────┤ │
│ │ • 特定业务逻辑 │ │
│ │ • 使用 Hooks │ │
│ │ • 可组合 │ │
│ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 布局组件 │ │ 高阶组件 │ │
│ │ (Layout) │ │ (HOC) │ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ • 页面结构 │ │ • 增强现有组件 │ │
│ │ • Header/Footer │ │ • 注入逻辑 │ │
│ │ • Sidebar/Content│ │ • 装饰器模式 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
React 组件实践
// components/
// ├── ui/ # 基础 UI 组件
// │ ├── Button/
// │ ├── Input/
// │ ├── Modal/
// │ └── ...
// │
// ├── business/ # 业务组件
// │ ├── UserCard/
// │ ├── ProductList/
// │ └── ...
// │
// └── layout/ # 布局组件
// ├── AppLayout/
// ├── DashboardLayout/
// └── ...
// components/ui/Button/Button.tsx
import React from 'react';
import styles from './Button.module.css';
type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost';
type ButtonSize = 'sm' | 'md' | 'lg';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
size?: ButtonSize;
loading?: boolean;
fullWidth?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
loading = false,
fullWidth = false,
leftIcon,
rightIcon,
children,
disabled,
className,
...props
}) => {
const classNames = [
styles.button,
styles[variant],
styles[size],
fullWidth && styles.fullWidth,
loading && styles.loading,
className
].filter(Boolean).join(' ');
return (
<button
className={classNames}
disabled={disabled || loading}
{...props}
>
{loading && <span className={styles.spinner} />}
{leftIcon && <span className={styles.icon}>{leftIcon}</span>}
<span>{children}</span>
{rightIcon && <span className={styles.icon}>{rightIcon}</span>}
</button>
);
};
// index.ts - 统一导出
export { Button } from './Button';
export type { ButtonProps } from './Button';
// components/ui/Button/index.ts
export { Button } from './Button';
export type { ButtonProps } from './Button';
组件间通信
Props 传递
// 父组件向子组件传值
function Parent() {
const [count, setCount] = useState(0);
return (
<Child
count={count}
onIncrement={() => setCount(c => c + 1)}
title="Counter"
/>
);
}
// 子组件接收
interface ChildProps {
count: number;
onIncrement: () => void;
title: string;
}
function Child({ count, onIncrement, title }: ChildProps) {
return (
<div>
<h1>{title}: {count}</h1>
<button onClick={onIncrement}>+1</button>
</div>
);
}
Context 跨层传递
// ThemeContext.tsx
const ThemeContext = createContext<ThemeContextType | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => {
setTheme(t => t === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// 使用
function ThemedButton() {
const { theme, toggleTheme } = useTheme();
return (
<button
className={theme === 'dark' ? 'dark' : 'light'}
onClick={toggleTheme}
>
Toggle Theme
</button>
);
}
自定义事件(发布-订阅)
// EventBus.ts
type EventCallback<T = any> = (data: T) => void;
class EventBus {
private listeners: Map<string, Set<EventCallback>> = new Map();
on<T>(event: string, callback: EventCallback<T>) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(callback);
// 返回取消订阅函数
return () => {
this.listeners.get(event)?.delete(callback);
};
}
emit<T>(event: string, data: T) {
this.listeners.get(event)?.forEach(cb => cb(data));
}
off(event: string, callback: EventCallback) {
this.listeners.get(event)?.delete(callback);
}
}
export const eventBus = new EventBus();
// 使用
// 订阅
const unsubscribe = eventBus.on('user:login', (user) => {
console.log('User logged in:', user);
});
// 发布
eventBus.emit('user:login', { id: 1, name: 'John' });
// 取消订阅
unsubscribe();
组件性能优化
React.memo
// 包装组件,避免不必要的重渲染
const MemoizedComponent = React.memo(Component, (prevProps, nextProps) => {
// 返回 true 表示不需要重渲染
return prevProps.items.length === nextProps.items.length;
});
useMemo 和 useCallback
function ExpensiveComponent({ items, filter }) {
// 缓存计算结果
const filteredItems = useMemo(() => {
return items.filter(item => item.name.includes(filter));
}, [items, filter]);
// 缓存回调函数
const handleClick = useCallback((id) => {
console.log('Clicked:', id);
}, []);
return (
<ul>
{filteredItems.map(item => (
<Item key={item.id} item={item} onClick={handleClick} />
))}
</ul>
);
}
虚拟列表
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<Item item={items[index]} />
</div>
)}
</FixedSizeList>
);
}
这一章想说的
模块化和组件化是前端架构的基石:
- ES Module:现代标准的前端模块系统,理解其作用域和导入/导出机制
- CSS 模块化:CSS Modules、Styled Components、Tailwind 等方案各有优劣
- 组件设计原则:单一职责、开闭原则、依赖倒置
- 组件分类:展示组件、容器组件、业务组件、布局组件
- 组件通信:Props 传递、Context、自定义事件(发布-订阅)
良好的组件化架构让代码更易维护、复用和测试。