模块化与组件化架构

深入理解前端模块化发展史、ES Module、CSS 模块化、以及组件化架构的设计原则与实践。


模块化的演进

前端模块化发展历程

┌──────────────────────────────────────────────────────────────┐
│                   前端模块化演进                               │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  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>
  );
}

这一章想说的

模块化和组件化是前端架构的基石:

  1. ES Module:现代标准的前端模块系统,理解其作用域和导入/导出机制
  2. CSS 模块化:CSS Modules、Styled Components、Tailwind 等方案各有优劣
  3. 组件设计原则:单一职责、开闭原则、依赖倒置
  4. 组件分类:展示组件、容器组件、业务组件、布局组件
  5. 组件通信:Props 传递、Context、自定义事件(发布-订阅)

良好的组件化架构让代码更易维护、复用和测试。


延展阅读