设计模式在React中的应用

115 阅读5分钟

在 React 中应用设计模式可以提高代码可维护性、复用性和可扩展性,以下总结一些设计模式在React中的应用。

1、 组件组合模式 (Component Composition)

  • 核心思想:通过 props 组合组件,避免复杂的继承结构
  • 优势:解耦容器与内容,增强组件复用性
// 父组件使用children prop组合
const Card = ({ title, children }) => (
  <div className="card">
    <h2>{title}</h2>
    <div className="content">{children}</div>
  </div>
);

// 使用
<Card title="用户信息">
  <Avatar />
  <UserDetails />
</Card>

2、高阶组件 (HOC)

  • 核心思想:通过函数返回新的组件,实现对组件功能的扩展
  • 优势:复用组件逻辑,减少重复代码,提高组件可维护性
  • 场景:Redux 的 connect、React Router 的 withRouter
const withLogger = (WrappedComponent) => {
    return (props) => {
        useEffect(() => {
            console.log(`${WrappedComponent.name} mounted`);
        }, []);
        return <WrappedComponent {...props} />;
    };
};

// 使用
const EnhancedButton = withLogger(Button);
<EnhancedButton />

3、渲染属性 (Render Props)

  • 核心思想:通过函数 prop 共享组件逻辑,实现组件的解耦
  • 优势:比 HOC 更灵活,避免组件嵌套地狱
<MouseTracker>
  {({ x, y }) => (
    <div>
      鼠标位置: {x}, {y}
    </div>
  )}
</MouseTracker>

// 实现
const MouseTracker = ({ children }) => {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  // ...监听鼠标移动逻辑
  return children(pos); // 关键:调用函数prop
};

4、 提供者模式 (Provider Pattern)

  • 核心思想:使用 Context API 跨层级传递数据
  • 优势:避免了 props drilling,使组件树更清晰
  • 场景:主题切换、用户身份、多语言
const ThemeContext = React.createContext();

// 顶层提供数据
<ThemeContext.Provider value="dark">
  <App />
</ThemeContext.Provider>

// 子组件消费
const Button = () => {
  const theme = useContext(ThemeContext);
  return <button className={`btn-${theme}`}>按钮</button>;
};

5、状态机模式 (State Machine)

  • 核心思想:使用状态机管理复杂状态流转
import { useReducer } from 'react';

const reducer = (state, action) => {
  switch (state.status) {
    case 'idle':
      return action.type === 'FETCH' ? { status: 'loading' } : state;
    case 'loading':
      return action.type === 'SUCCESS' 
        ? { status: 'success', data: action.payload }
        : { status: 'error', error: action.error };
    default:
      return state;
  }
};

// 组件内使用
const [state, dispatch] = useReducer(reducer, { status: 'idle' });

6、复合组件 (Compound Components)

  • 核心思想:关联组件共享隐式状态
  • 优势:简化 API 设计
// 定义
const Tabs = ({ children }) => {
  const [activeIndex, setActiveIndex] = useState(0);
  return React.Children.map(children, (child, index) =>
    React.cloneElement(child, {
      isActive: index === activeIndex,
      onClick: () => setActiveIndex(index)
    })
  );
};

// 子组件无需单独传递状态
const TabItem = ({ isActive, onClick, children }) => (
  <div 
    className={`tab ${isActive ? 'active' : ''}`} 
    onClick={onClick}
  >
    {children}
  </div>
);

// 使用
<Tabs>
  <TabItem>标签1</TabItem>
  <TabItem>标签2</TabItem>
</Tabs>

7、观察者模式 & 发布订阅

  • 核心思想:通过事件监听机制实现数据更新通知
  • 优势:解耦组件间通信,提高组件可维护性
// 创建事件总线
const EventBus = {
  listeners: {},
  emit(event, data) {
    (this.listeners[event] || []).forEach(fn => fn(data));
  },
  on(event, callback) {
    this.listeners[event] = [...(this.listeners[event] || []), callback];
  },
  off(event, callback) {
    this.listeners[event] = this.listeners[event].filter(fn => fn !== callback);
  }
};

// 组件A触发事件
EventBus.emit('dataUpdate', { newData: 123 });

// 组件B监听
useEffect(() => {
  const handler = data => console.log(data);
  EventBus.on('dataUpdate', handler);
  return () => EventBus.off('dataUpdate', handler); // 清理
}, []);

8、策略模式

  • 核心思想:预设策略集,通过传入参数选择执行策略
// 策略集
const formatters = {
  date: (value) => new Date(value).toLocaleDateString(),
  currency: (value) => `$${parseFloat(value).toFixed(2)}`,
  default: (value) => value
};

// 使用策略
const FormattedText = ({ type, value }) => (
  <span>{formatters[type]?.(value) || formatters.default(value)}</span>
);

// 调用
<FormattedText type="currency" value="9.99" />

9、懒加载模式 (Lazy Initialization)

  • 核心思想:在需要时才初始化对象或资源
  • 优势:减少初始化开销,提高性能
import React, { lazy, Suspense } from 'react';

const LazyDashboard = lazy(() => import('./Dashboard'));

const App = () => (
  <Suspense fallback={<div>加载中...</div>}>
    {userLoggedIn && <LazyDashboard />}
  </Suspense>
);

10、享元模式 (Flyweight)

  • 核心思想:复用对象,减少内存占用
  • 优势:提高性能,减少内存占用
// 共享图标组件
const icons = {
    star: <StarIcon />,
    heart: <HeartIcon />,
    // ...
};

const Icon = ({ type }) => {
  return icons[type] || <DefaultIcon />;
};

// 复用相同实例
<Icon type="star" />
<Icon type="star" />  // 复用相同内存实例

11、原型模式 (Prototype)

  • 核心思想:通过Object.create()创建新对象
const defaultFormConfig = {
  validate: () => true,
  style: { padding: 10 },
  // ...
};

// 基于原型创建新表单
const LoginForm = () => {
  const formConfig = Object.create(defaultFormConfig);
  formConfig.fields = [/*...*/];
  
  return <Form config={formConfig} />;
};

12、职责链模式 (Chain of Responsibility)

  • 核心思想:多个对象依次处理请求
const errorHandlers = [
  (error) => isNetworkError(error) ? handleNetworkError(error) : null,
  (error) => isAuthError(error) ? handleAuthError(error) : null,
  (error) => handleGenericError(error) // 兜底处理
];

const processError = (error) => {
  for (const handler of errorHandlers) {
    const result = handler(error);
    if (result !== null) return result;
  }
};

// 组件中使用
try { /* ... */ } 
catch (error) { processError(error); }

13、中介者模式 (Mediator)

  • 核心思想:通过中介者对象管理组件之间的通信
class ChatMediator {
  participants = [];
  
  register(participant) {
    this.participants.push(participant);
  }
  
  send(message, sender) {
    this.participants
      .filter(p => p !== sender)
      .forEach(p => p.receive(message));
  }
}

// 组件中
const ChatUser = ({ name, mediator }) => {
  useEffect(() => {
    mediator.register({ receive: handleMessage, name });
  }, []);
  
  const send = () => mediator.send(text, name);
};

14、 空对象模式 (Null Object)

  • 核心思想:提供默认对象避免空值检查
const NullComponent = () => null;

const getUserComponent = (user) => {
  if (!user) return NullComponent;
  if (user.type === 'admin') return AdminPanel;
  return UserPanel;
};

const UserSection = ({ user }) => {
  const UserComponent = getUserComponent(user);
  return <UserComponent />; // 无需检查空值
};

15、访问者模式 (Visitor)

  • 核心思想:定义新操作而不修改元素类
  • 优势:扩展性高,避免了修改元素类
// 数据形状
const elements = [
  { type: 'text', content: 'Hello' },
  { type: 'image', src: 'logo.png' }
];

// 访问器实现
const renderers = {
  text: (element) => <Text content={element.content} />,
  image: (element) => <Image src={element.src} />,
  default: () => <FallbackComponent />
};

// 渲染器
const Renderer = ({ elements }) => (
  <>
    {elements.map(element => {
      const render = renderers[element.type] || renderers.default;
      return render(element);
    })}
  </>
);

16、命令模式 (Command)

  • 核心思想:将请求封装为对象,实现请求的排队、记录、撤销和重做功能
  • 优势:解耦命令与执行者,提高代码可读性
import React, { useState } from 'react';

class CommandManager {
    constructor() {
        this.history = [];
        this.position = -1;
    }

    execute(command) {
        // 清除当前位置之后的历史
        if (this.position < this.history.length - 1) {
            this.history = this.history.slice(0, this.position + 1);
        }

        command.execute();
        this.history.push(command);
        this.position = this.history.length - 1;
    }

    undo() {
        if (this.position >= 0) {
            this.history[this.position].undo();
            this.position--;
        }
    }

    redo() {
        if (this.position < this.history.length - 1) {
            this.position++;
            this.history[this.position].execute();
        }
    }

    get canUndo() {
        return this.position >= 0;
    }

    get canRedo() {
        return this.position < this.history.length - 1;
    }
}

// 命令基类
class Command {
    constructor(execute, undo, data) {
        this.execute = () => execute(data);
        this.undo = () => undo(data);
    }
}

const UndoableCounter = () => {
    const [count, setCount] = useState(0);
    const [commandManager] = useState(() => new CommandManager());

    // 执行命令函数
    const executeCommand = (action, value) => {
        const newValue = action === 'increment' ? count + value : count - value;

        const command = new Command(
            (data) => setCount(data.newValue),
            (data) => setCount(data.oldValue),
            {
                oldValue: count,
                newValue,
                action,
                value
            }
        );

        commandManager.execute(command);
    };

    // 操作按钮
    const ActionButton = ({ action, value, children }) => (
        <button onClick={() => executeCommand(action, value)}>
            {children}
        </button>
    );

    return (
        <div className="undoable-counter">
            <h2>可撤销计数器</h2>
            <div className="counter-display">{count}</div>

            <div className="controls">
                <ActionButton action="increment" value={1}>
                    +1
                </ActionButton>
                <ActionButton action="increment" value={5}>
                    +5
                </ActionButton>
                <ActionButton action="decrement" value={1}>
                    -1
                </ActionButton>
                <ActionButton action="decrement" value={5}>
                    -5
                </ActionButton>

                <button
                    onClick={commandManager.undo.bind(commandManager)}
                    disabled={!commandManager.canUndo}
                    className="undo-btn"
                >
                    撤销
                </button>
                <button
                    onClick={commandManager.redo.bind(commandManager)}
                    disabled={!commandManager.canRedo}
                    className="redo-btn"
                >
                    重做
                </button>
            </div>

            <div className="command-history">
                <h3>操作历史:</h3>
                <ul>
                    {commandManager.history.map((command, index) => (
                        <li
                            key={index}
                            className={index === commandManager.position ? 'current' : ''}
                        >
                            {command.data.action} {command.data.value}
                        </li>
                    ))}
                </ul>
            </div>
        </div>
    );
};

export default UndoableCounter;

17、迭代器模式 (Iterator)

  • 核心思想:提供一致的数据遍历接口
  • 优势:隐藏聚合对象的内部结构,提高代码可读性
function* paginatedIterator(fetchPage) {
  let page = 0;
  let hasMore = true;

  while (hasMore) {
      // 等待获取一页数据
      const response = yield fetchPage(page);
      // 更新下一页和是否还有更多数据
      hasMore = response.hasMore;
      page += 1;
  }  
};

// 组件中使用
useEffect(() => {
  // 初始化生成器
  const paginator = paginatedFetcher(fetchPage);
  // 启动生成器,获取第一个yield返回的值(即第一个fetchPage(0))
  const { value } = paginator.next();
  setUsers(prev => [...prev, ...value]);
}, []);

18、依赖注入模式 (Dependency Injection)

  • 核心思想:通过上下文或Props注入服务依赖,以解耦组件
  • 优势:提高代码可测试性,解耦组件
// 创建服务上下文
const ApiServiceContext = React.createContext();

// 顶层注入服务实现
<ApiServiceContext.Provider value={new ApiService()}>
  <UserProfile />
</ApiServiceContext.Provider>

// 子组件消费服务
const UserProfile = () => {
  const api = useContext(ApiServiceContext);
  const [user, setUser] = useState(null);

  useEffect(() => {
    api.getUser().then(setUser);
  }, []);

  return <div>{user?.name}</div>;
};

19、代理模式 (Proxy)

  • 核心思想:创建代理组件控制原始组件访问
  • 优势:控制组件访问,实现权限控制
// 权限代理组件
const withAuth = (WrappedComponent) => (props) => {
  const { isAuthenticated } = useAuth();
  
  return isAuthenticated 
    ? <WrappedComponent {...props} />
    : <div>请先登录</div>;
};

// 使用代理
const PrivateDashboard = withAuth(Dashboard);

20、外观模式 (Facade Pattern)

  • 核心思想:提供简化的接口,隐藏内部复杂逻辑
  • 优势:简化接口,提高代码可读性
import React, { useState, useMemo } from 'react';

// 库存子系统
const InventoryService = {
  checkAvailability: (productId, quantity) => {
    // 模拟库存检查
    return new Promise((resolve) => {
      setTimeout(() => {
        const inStock = Math.random() > 0.3; // 70%概率有库存
        resolve(inStock);
      }, 300);
    });
  },

  updateInventory: (productId, quantity) => {
    // 模拟库存更新
    console.log(`更新库存: 产品 ${productId}, 数量 -${quantity}`);
    return Promise.resolve();
  }
};

// 支付子系统
const PaymentService = {
  authorizePayment: (amount, cardDetails) => {
    // 模拟支付授权
    return new Promise((resolve) => {
      setTimeout(() => {
        const success = Math.random() > 0.2; // 80%支付成功概率
        resolve({ success, transactionId: success ? `tx-${Math.random().toString(36).substring(2, 10)}` : null });
      }, 500);
    });
  }
};

// 物流子系统
const ShippingService = {
  scheduleDelivery: (address, items) => {
    // 模拟发货安排
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({ trackingId: `trk-${Math.random().toString(36).substring(2, 14)}`, estimatedDelivery: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000) });
      }, 400);
    });
  }
};

// 外观服务 - 提供统一的结账接口
class CheckoutFacade {
  static async checkout(orderData) {
    const { items, paymentMethod, shippingAddress } = orderData;
    
    try {
      // 1. 检查库存
      const availabilityChecks = await Promise.all(
        items.map(item => InventoryService.checkAvailability(item.productId, item.quantity))
      );
      
      if (availabilityChecks.some(available => !available)) {
        throw new Error('某些商品库存不足');
      }
      
      // 2. 处理支付
      const totalAmount = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
      const paymentResult = await PaymentService.authorizePayment(totalAmount, paymentMethod);
      
      if (!paymentResult.success) {
        throw new Error('支付授权失败');
      }
      
      // 3. 安排物流
      const shippingResult = await ShippingService.scheduleDelivery(shippingAddress, items);
      
      // 4. 更新库存
      await Promise.all(
        items.map(item => InventoryService.updateInventory(item.productId, item.quantity))
      );
      
      return {
        success: true,
        transactionId: paymentResult.transactionId,
        trackingId: shippingResult.trackingId,
        estimatedDelivery: shippingResult.estimatedDelivery.toISOString().split('T')[0]
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
}

const CheckoutPage = () => {
  const [cartItems] = useState([
    { productId: 'p1', name: '商品 A', price: 29.99, quantity: 2 },
    { productId: 'p2', name: '商品 B', price: 49.99, quantity: 1 }
  ]);
  
  const [shippingInfo, setShippingInfo] = useState({
    name: '',
    address: '',
    city: ''
  });
  
  const [paymentInfo, setPaymentInfo] = useState({
    cardNumber: '',
    expiry: '',
    cvv: ''
  });
  
  const [checkoutResult, setCheckoutResult] = useState(null);
  const [loading, setLoading] = useState(false);
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    
    const orderData = {
      items: cartItems,
      shippingAddress: shippingInfo,
      paymentMethod: paymentInfo
    };
    
    const result = await CheckoutFacade.checkout(orderData);
    setCheckoutResult(result);
    setLoading(false);
  };
  
  const totalAmount = useMemo(() => {
    return cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }, [cartItems]);
  
  const updateShipping = (field, value) => {
    setShippingInfo(prev => ({ ...prev, [field]: value }));
  };
  
  const updatePayment = (field, value) => {
    setPaymentInfo(prev => ({ ...prev, [field]: value }));
  };
  
  if (loading) {
    return (
      <div className="checkout-loading">
        <h2>处理结账中...</h2>
        <div className="spinner"></div>
      </div>
    );
  }
  
  if (checkoutResult) {
    return (
      <div className="checkout-result">
        {checkoutResult.success ? (
          <>
            <h2 className="success">订单创建成功!</h2>
            <div className="order-details">
              <p>交易号: {checkoutResult.transactionId}</p>
              <p>物流跟踪号: {checkoutResult.trackingId}</p>
              <p>预计送达时间: {checkoutResult.estimatedDelivery}</p>
            </div>
            <button onClick={() => setCheckoutResult(null)}>返回</button>
          </>
        ) : (
          <>
            <h2 className="error">结账失败</h2>
            <p>{checkoutResult.error}</p>
            <button onClick={() => setCheckoutResult(null)}>重试</button>
          </>
        )}
      </div>
    );
  }
  
  return (
    <div className="checkout-container">
      <h2>结账</h2>
      
      <form onSubmit={handleSubmit}>
        <div className="checkout-section">
          <h3>配送信息</h3>
          <div className="form-group">
            <label>姓名:</label>
            <input 
              type="text" 
              value={shippingInfo.name}
              onChange={(e) => updateShipping('name', e.target.value)}
              required
            />
          </div>
          <div className="form-group">
            <label>地址:</label>
            <input 
              type="text" 
              value={shippingInfo.address}
              onChange={(e) => updateShipping('address', e.target.value)}
              required
            />
          </div>
          <div className="form-group">
            <label>城市:</label>
            <input 
              type="text" 
              value={shippingInfo.city}
              onChange={(e) => updateShipping('city', e.target.value)}
              required
            />
          </div>
        </div>
        
        <div className="checkout-section">
          <h3>支付信息</h3>
          <div className="form-group">
            <label>卡号:</label>
            <input 
              type="text" 
              value={paymentInfo.cardNumber}
              onChange={(e) => updatePayment('cardNumber', e.target.value)}
              placeholder="1234 5678 9012 3456"
              required
            />
          </div>
          <div className="form-group-row">
            <div className="form-group">
              <label>有效期:</label>
              <input 
                type="text" 
                value={paymentInfo.expiry}
                onChange={(e) => updatePayment('expiry', e.target.value)}
                placeholder="MM/YY"
                required
              />
            </div>
            <div className="form-group">
              <label>CVV:</label>
              <input 
                type="text" 
                value={paymentInfo.cvv}
                onChange={(e) => updatePayment('cvv', e.target.value)}
                placeholder="123"
                required
              />
            </div>
          </div>
        </div>
        
        <div className="checkout-summary">
          <h3>订单摘要</h3>
          <ul className="cart-items">
            {cartItems.map((item, index) => (
              <li key={index}>
                {item.name} × {item.quantity}: ${(item.price * item.quantity).toFixed(2)}
              </li>
            ))}
          </ul>
          <div className="total-amount">
            总计: <strong>${totalAmount.toFixed(2)}</strong>
          </div>
        </div>
        
        <button type="submit" className="submit-btn">
          确认支付
        </button>
      </form>
    </div>
  );
};

export default CheckoutPage;