React编程的核心概念:发布-订阅模型、背压与异步非阻塞

文章目录

    • [2.3 发布-订阅模型(Pub-Sub)](#2.3 发布-订阅模型(Pub-Sub))
      • [2.3.1 发布-订阅模型基础理论](#2.3.1 发布-订阅模型基础理论)
      • [2.3.2 React中的发布-订阅实现](#2.3.2 React中的发布-订阅实现)
        • [2.3.2.1 自定义事件总线实现](#2.3.2.1 自定义事件总线实现)
        • [2.3.2.2 使用RxJS实现响应式Pub-Sub](#2.3.2.2 使用RxJS实现响应式Pub-Sub)
      • [2.3.3 现代状态管理库中的Pub-Sub](#2.3.3 现代状态管理库中的Pub-Sub)
      • [2.3.4 Pub-Sub模式的高级应用](#2.3.4 Pub-Sub模式的高级应用)
        • [2.3.4.1 多主题复杂订阅](#2.3.4.1 多主题复杂订阅)
        • [2.3.4.2 带历史记录的Event Bus](#2.3.4.2 带历史记录的Event Bus)
      • [2.3.5 Pub-Sub模式的优缺点与最佳实践](#2.3.5 Pub-Sub模式的优缺点与最佳实践)
    • [2.4 背压(Backpressure)](#2.4 背压(Backpressure))
      • [2.4.1 背压概念解析](#2.4.1 背压概念解析)
      • [2.4.2 React中的背压处理技术](#2.4.2 React中的背压处理技术)
        • [2.4.2.1 防抖(Debounce)与节流(Throttle)](#2.4.2.1 防抖(Debounce)与节流(Throttle))
        • [2.4.2.2 使用RxJS处理背压](#2.4.2.2 使用RxJS处理背压)
      • [2.4.3 复杂数据流的背压管理](#2.4.3 复杂数据流的背压管理)
        • [2.4.3.1 分页加载大数据集](#2.4.3.1 分页加载大数据集)
        • [2.4.3.2 WebSocket高频数据处理](#2.4.3.2 WebSocket高频数据处理)
      • [2.4.4 背压处理策略比较](#2.4.4 背压处理策略比较)
      • [2.4.5 背压处理最佳实践](#2.4.5 背压处理最佳实践)
    • [2.5 异步与非阻塞(Async & Non-blocking)](#2.5 异步与非阻塞(Async & Non-blocking))
      • [2.5.1 异步编程基础](#2.5.1 异步编程基础)
      • [2.5.2 React异步处理机制](#2.5.2 React异步处理机制)
        • [2.5.2.1 使用useEffect处理副作用](#2.5.2.1 使用useEffect处理副作用)
        • [2.5.2.2 使用useReducer管理复杂异步状态](#2.5.2.2 使用useReducer管理复杂异步状态)
      • [2.5.3 高级异步模式](#2.5.3 高级异步模式)
        • [2.5.3.1 竞态条件处理](#2.5.3.1 竞态条件处理)
        • [2.5.3.2 并行与顺序请求](#2.5.3.2 并行与顺序请求)
      • [2.5.4 非阻塞UI模式](#2.5.4 非阻塞UI模式)
        • [2.5.4.1 过渡与Suspense](#2.5.4.1 过渡与Suspense)
        • [2.5.4.2 使用useTransition优化用户体验](#2.5.4.2 使用useTransition优化用户体验)
      • [2.5.5 异步最佳实践](#2.5.5 异步最佳实践)
    • 总结

2.3 发布-订阅模型(Pub-Sub)

2.3.1 发布-订阅模型基础理论

发布-订阅模型(Publish-Subscribe Pattern,简称Pub-Sub)是一种消息传递范式,发送者(发布者)不直接将消息发送给特定接收者(订阅者),而是将发布的消息分为不同的类别,订阅者只接收感兴趣类别的消息。这种模式与观察者模式类似,但更加解耦,发布者完全不知道订阅者的存在。

Pub-Sub模型的核心组件:

  1. 发布者(Publisher):负责产生和发布消息到消息代理
  2. 订阅者(Subscriber):向消息代理注册感兴趣的主题,并接收相关消息
  3. 消息代理(Broker):作为中间人,负责接收发布者的消息并分发给所有相关订阅者
  4. 主题(Topic):消息的分类标识,订阅者根据主题选择接收哪些消息

2.3.2 React中的发布-订阅实现

在React生态系统中,发布-订阅模型有多种实现方式,从简单的自定义事件总线到复杂的状态管理库。

2.3.2.1 自定义事件总线实现
jsx 复制代码
class EventBus {
  constructor() {
    this.events = {};
  }

  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
    
    return () => {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    };
  }

  publish(event, ...args) {
    if (!this.events[event]) return;
    this.events[event].forEach(callback => callback(...args));
  }

  unsubscribe(event, callback) {
    if (!this.events[event]) return;
    this.events[event] = this.events[event].filter(cb => cb !== callback);
  }
}

// 全局事件总线实例
const eventBus = new EventBus();

// 发布者组件
function Publisher() {
  const handleClick = () => {
    eventBus.publish('dataUpdate', { time: Date.now(), value: Math.random() });
  };

  return <button onClick={handleClick}>发布数据</button>;
}

// 订阅者组件
function Subscriber() {
  const [message, setMessage] = useState('无数据');

  useEffect(() => {
    const unsubscribe = eventBus.subscribe('dataUpdate', (data) => {
      setMessage(`收到数据: ${JSON.stringify(data)}`);
    });
    
    return () => unsubscribe();
  }, []);

  return <div>{message}</div>;
}

function App() {
  return (
    <div>
      <Publisher />
      <Subscriber />
    </div>
  );
}
2.3.2.2 使用RxJS实现响应式Pub-Sub

RxJS是一个强大的响应式编程库,非常适合实现复杂的Pub-Sub场景:

jsx 复制代码
import { Subject } from 'rxjs';

// 创建主题
const dataSubject = new Subject();

// 发布者组件
function RxPublisher() {
  const emitData = () => {
    dataSubject.next({
      id: Math.floor(Math.random() * 1000),
      timestamp: new Date().toISOString()
    });
  };

  return <button onClick={emitData}>发射数据</button>;
}

// 订阅者组件
function RxSubscriber() {
  const [items, setItems] = useState([]);

  useEffect(() => {
    const subscription = dataSubject.subscribe(data => {
      setItems(prev => [...prev, data]);
    });
    
    return () => subscription.unsubscribe();
  }, []);

  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>
          ID: {item.id} - Time: {item.timestamp}
        </li>
      ))}
    </ul>
  );
}

function RxApp() {
  return (
    <div>
      <RxPublisher />
      <RxSubscriber />
    </div>
  );
}

2.3.3 现代状态管理库中的Pub-Sub

Redux等状态管理库的核心也基于Pub-Sub模式:

jsx 复制代码
import { createStore } from 'redux';

// Reducer处理action并返回新状态
function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      return state;
  }
}

// 创建store
const store = createStore(counterReducer);

// 发布action的函数
function dispatchAction(type) {
  return () => store.dispatch({ type });
}

// 订阅者组件
function ReduxSubscriber() {
  const [count, setCount] = useState(store.getState().value);

  useEffect(() => {
    const unsubscribe = store.subscribe(() => {
      setCount(store.getState().value);
    });
    
    return () => unsubscribe();
  }, []);

  return <div>当前计数: {count}</div>;
}

function ReduxApp() {
  return (
    <div>
      <button onClick={dispatchAction('INCREMENT')}>增加</button>
      <button onClick={dispatchAction('DECREMENT')}>减少</button>
      <ReduxSubscriber />
    </div>
  );
}

2.3.4 Pub-Sub模式的高级应用

2.3.4.1 多主题复杂订阅
jsx 复制代码
const multiEventBus = {
  topics: {},
  
  subscribe(topic, callback) {
    if (!this.topics[topic]) this.topics[topic] = [];
    this.topics[topic].push(callback);
    
    return () => {
      this.topics[topic] = this.topics[topic].filter(cb => cb !== callback);
    };
  },
  
  publish(topic, data) {
    if (!this.topics[topic]) return;
    this.topics[topic].forEach(cb => cb(data));
  },
  
  publishAll(data) {
    Object.values(this.topics).flat().forEach(cb => cb(data));
  }
};

function MultiPublisher() {
  const publishUser = () => multiEventBus.publish('user', { name: 'Alice', age: 25 });
  const publishProduct = () => multiEventBus.publish('product', { id: 1, name: 'Laptop' });
  const publishToAll = () => multiEventBus.publishAll({ type: 'SYSTEM', message: 'Refresh all' });

  return (
    <div>
      <button onClick={publishUser}>发布用户数据</button>
      <button onClick={publishProduct}>发布产品数据</button>
      <button onClick={publishToAll}>全局通知</button>
    </div>
  );
}

function UserSubscriber() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    return multiEventBus.subscribe('user', setUser);
  }, []);

  return <div>用户: {user ? JSON.stringify(user) : '无'}</div>;
}

function ProductSubscriber() {
  const [product, setProduct] = useState(null);

  useEffect(() => {
    return multiEventBus.subscribe('product', setProduct);
  }, []);

  return <div>产品: {product ? JSON.stringify(product) : '无'}</div>;
}

function GlobalSubscriber() {
  const [message, setMessage] = useState('等待全局消息...');

  useEffect(() => {
    const unsubUser = multiEventBus.subscribe('user', () => setMessage('收到用户更新'));
    const unsubProduct = multiEventBus.subscribe('product', () => setMessage('收到产品更新'));
    const unsubAll = multiEventBus.subscribe('*', (data) => setMessage(`全局: ${data.message}`));
    
    return () => {
      unsubUser();
      unsubProduct();
      unsubAll();
    };
  }, []);

  return <div>{message}</div>;
}

function MultiPubSubApp() {
  return (
    <div>
      <MultiPublisher />
      <UserSubscriber />
      <ProductSubscriber />
      <GlobalSubscriber />
    </div>
  );
}
2.3.4.2 带历史记录的Event Bus
jsx 复制代码
class HistoryEventBus {
  constructor() {
    this.events = {};
    this.history = new Map();
  }

  subscribe(event, callback) {
    if (!this.events[event]) this.events[event] = [];
    this.events[event].push(callback);
    
    // 如果有历史记录,立即通知
    if (this.history.has(event)) {
      callback(this.history.get(event));
    }
    
    return () => {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    };
  }

  publish(event, data, saveToHistory = false) {
    if (saveToHistory) {
      this.history.set(event, data);
    }
    
    if (!this.events[event]) return;
    this.events[event].forEach(callback => callback(data));
  }

  getHistory(event) {
    return this.history.get(event);
  }
}

const historyBus = new HistoryEventBus();

function HistoryPublisher() {
  const [value, setValue] = useState('');

  const publishWithHistory = () => {
    historyBus.publish('withHistory', value, true);
  };

  const publishWithoutHistory = () => {
    historyBus.publish('noHistory', value, false);
  };

  return (
    <div>
      <input value={value} onChange={e => setValue(e.target.value)} />
      <button onClick={publishWithHistory}>发布(保存历史)</button>
      <button onClick={publishWithoutHistory}>发布(不保存)</button>
    </div>
  );
}

function HistorySubscriber() {
  const [withHistory, setWithHistory] = useState('');
  const [noHistory, setNoHistory] = useState('');

  useEffect(() => {
    const unsub1 = historyBus.subscribe('withHistory', setWithHistory);
    const unsub2 = historyBus.subscribe('noHistory', setNoHistory);
    
    // 获取历史记录
    const historyValue = historyBus.getHistory('withHistory');
    if (historyValue) {
      setWithHistory(`历史记录: ${historyValue}`);
    }
    
    return () => {
      unsub1();
      unsub2();
    };
  }, []);

  return (
    <div>
      <div>带历史: {withHistory || '无'}</div>
      <div>不带历史: {noHistory || '无'}</div>
    </div>
  );
}

function HistoryApp() {
  return (
    <div>
      <HistoryPublisher />
      <HistorySubscriber />
    </div>
  );
}

2.3.5 Pub-Sub模式的优缺点与最佳实践


优点

  1. 松耦合:发布者和订阅者完全解耦,互不知晓对方存在
  2. 可扩展性:可以轻松添加新的发布者或订阅者而不影响现有系统
  3. 灵活性:支持一对多、多对多通信模式
  4. 动态性:订阅关系可以在运行时动态建立和解除

缺点

  1. 调试困难:消息流可能难以追踪,特别是复杂的发布订阅关系
  2. 性能问题:大量消息可能导致系统性能下降
  3. 消息顺序:不能保证消息的接收顺序与发送顺序一致
  4. 内存泄漏:忘记取消订阅可能导致内存泄漏

最佳实践

  1. 合理设计主题结构:避免主题过于宽泛或过于具体
  2. 使用强类型:为消息定义明确的类型和结构
  3. 及时取消订阅:在组件卸载时务必取消订阅
  4. 限制消息量:避免高频发布大量小消息
  5. 考虑错误处理:设计良好的错误处理机制
  6. 文档化消息协议:清晰记录所有主题和消息格式

2.4 背压(Backpressure)

2.4.1 背压概念解析

背压(Backpressure)是流处理系统中的一种重要概念,指当下游处理速度跟不上上游数据产生速度时,系统需要采取的策略和机制来应对这种不平衡。在React和前端开发中,背压处理尤为重要,因为浏览器环境对资源使用有严格限制。

背压问题常见场景:

  1. WebSocket高频数据推送
  2. 大规模实时数据可视化
  3. 文件上传/下载处理
  4. 高频率的用户事件(如滚动、鼠标移动)
  5. 与后端的高频轮询通信

2.4.2 React中的背压处理技术

2.4.2.1 防抖(Debounce)与节流(Throttle)
jsx 复制代码
import { useState, useEffect } from 'react';
import { debounce, throttle } from 'lodash';

function ScrollMonitor() {
  const [scrollPosition, setScrollPosition] = useState(0);
  const [debouncedPos, setDebouncedPos] = useState(0);
  const [throttledPos, setThrottledPos] = useState(0);

  useEffect(() => {
    const handleScroll = () => {
      const position = window.pageYOffset;
      setScrollPosition(position);
    };
    
    const handleDebouncedScroll = debounce(() => {
      setDebouncedPos(window.pageYOffset);
    }, 200);
    
    const handleThrottledScroll = throttle(() => {
      setThrottledPos(window.pageYOffset);
    }, 200);
    
    window.addEventListener('scroll', handleScroll);
    window.addEventListener('scroll', handleDebouncedScroll);
    window.addEventListener('scroll', handleThrottledScroll);
    
    return () => {
      window.removeEventListener('scroll', handleScroll);
      window.removeEventListener('scroll', handleDebouncedScroll);
      window.removeEventListener('scroll', handleThrottledScroll);
      handleDebouncedScroll.cancel();
      handleThrottledScroll.cancel();
    };
  }, []);

  return (
    <div style={{ height: '2000px' }}>
      <div style={{ position: 'fixed', top: 0, left: 0, background: 'white' }}>
        <div>原始位置: {scrollPosition}</div>
        <div>防抖位置: {debouncedPos}</div>
        <div>节流位置: {throttledPos}</div>
      </div>
    </div>
  );
}
2.4.2.2 使用RxJS处理背压

RxJS提供了多种背压策略操作符:

jsx 复制代码
import { fromEvent, Subject } from 'rxjs';
import { throttleTime, auditTime, sampleTime, bufferCount } from 'rxjs/operators';

function RxBackpressure() {
  const [events, setEvents] = useState([]);
  const [subject] = useState(new Subject());

  useEffect(() => {
    const subscription = subject
      .pipe(
        // 选择一种背压策略
        // throttleTime(200), // 节流 - 每200ms最多一个值
        // auditTime(200),   // 审计 - 在200ms窗口结束时发出最新值
        // sampleTime(200),  // 采样 - 每200ms取一个样本值
        bufferCount(5)     // 缓冲 - 每5个值作为数组发出一次
      )
      .subscribe(value => {
        setEvents(prev => [...prev, value]);
      });
    
    const mouseMove$ = fromEvent(document, 'mousemove');
    const mouseSub = mouseMove$.subscribe(e => {
      subject.next({ x: e.clientX, y: e.clientY, time: Date.now() });
    });
    
    return () => {
      subscription.unsubscribe();
      mouseSub.unsubscribe();
    };
  }, [subject]);

  return (
    <div>
      <h2>鼠标移动事件(带背压处理)</h2>
      <div style={{ height: '500px', border: '1px solid #ccc' }}>
        {events.map((event, i) => (
          <div key={i}>
            {Array.isArray(event) 
              ? `批量: ${event.length}个事件` 
              : `位置: ${event.x}, ${event.y}`}
          </div>
        ))}
      </div>
    </div>
  );
}

2.4.3 复杂数据流的背压管理

2.4.3.1 分页加载大数据集
jsx 复制代码
function LargeDataLoader() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [page, setPage] = useState(1);
  const [hasMore, setHasMore] = useState(true);

  const loadMore = useCallback(async () => {
    if (loading || !hasMore) return;
    
    setLoading(true);
    try {
      // 模拟API调用
      await new Promise(resolve => setTimeout(resolve, 500));
      const newData = Array.from({ length: 20 }, (_, i) => 
        `项目 ${(page - 1) * 20 + i + 1}`
      );
      
      setData(prev => [...prev, ...newData]);
      setPage(prev => prev + 1);
      setHasMore(page < 5); // 假设总共5页数据
    } finally {
      setLoading(false);
    }
  }, [page, loading, hasMore]);

  const handleScroll = useCallback(() => {
    const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
    if (scrollHeight - (scrollTop + clientHeight) < 100) {
      loadMore();
    }
  }, [loadMore]);

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [handleScroll]);

  useEffect(() => {
    loadMore();
  }, []);

  return (
    <div>
      <h2>大数据集分页加载</h2>
      <ul style={{ height: '80vh', overflowY: 'auto' }}>
        {data.map((item, index) => (
          <li key={index} style={{ padding: '20px', borderBottom: '1px solid #eee' }}>
            {item}
          </li>
        ))}
        {loading && <li>加载中...</li>}
        {!hasMore && <li>没有更多数据了</li>}
      </ul>
    </div>
  );
}
2.4.3.2 WebSocket高频数据处理
jsx 复制代码
import { useState, useEffect, useRef } from 'react';

function WebSocketBackpressure() {
  const [messages, setMessages] = useState([]);
  const [displayMessages, setDisplayMessages] = useState([]);
  const [isConnected, setIsConnected] = useState(false);
  const [throttleEnabled, setThrottleEnabled] = useState(true);
  const wsRef = useRef(null);
  const lastUpdateRef = useRef(0);

  useEffect(() => {
    // 模拟WebSocket连接
    wsRef.current = {
      send: (message) => console.log('发送:', message),
      close: () => {
        setIsConnected(false);
        console.log('连接关闭');
      }
    };
    
    // 模拟接收消息
    const interval = setInterval(() => {
      if (isConnected) {
        const newMessage = {
          id: Date.now(),
          value: Math.random(),
          timestamp: new Date().toISOString()
        };
        setMessages(prev => [...prev, newMessage]);
      }
    }, 50); // 每秒约20条消息
    
    return () => clearInterval(interval);
  }, [isConnected]);

  useEffect(() => {
    if (!throttleEnabled) {
      setDisplayMessages(messages);
      return;
    }
    
    const interval = setInterval(() => {
      if (messages.length > displayMessages.length) {
        const newMessages = messages.slice(displayMessages.length);
        setDisplayMessages(prev => [...prev, ...newMessages.slice(0, 5)]); // 每次最多5条
      }
    }, 200); // 每秒更新5次,最多25条/秒
    
    return () => clearInterval(interval);
  }, [messages, displayMessages, throttleEnabled]);

  const toggleConnection = () => {
    setIsConnected(prev => !prev);
    if (!isConnected) {
      setMessages([]);
      setDisplayMessages([]);
    }
  };

  return (
    <div>
      <h2>WebSocket高频数据处理</h2>
      <div>
        <button onClick={toggleConnection}>
          {isConnected ? '断开连接' : '建立连接'}
        </button>
        <label>
          <input
            type="checkbox"
            checked={throttleEnabled}
            onChange={() => setThrottleEnabled(!throttleEnabled)}
          />
          启用背压处理
        </label>
      </div>
      <div>
        <p>接收消息数: {messages.length}</p>
        <p>显示消息数: {displayMessages.length}</p>
      </div>
      <div style={{ height: '300px', overflowY: 'auto', border: '1px solid #ccc' }}>
        {displayMessages.map(msg => (
          <div key={msg.id} style={{ padding: '5px', borderBottom: '1px solid #eee' }}>
            {msg.value.toFixed(4)} @ {msg.timestamp}
          </div>
        ))}
      </div>
    </div>
  );
}

2.4.4 背压处理策略比较

策略 描述 适用场景 React实现示例
防抖(Debounce) 事件触发后等待一段时间再处理,若期间有新事件则重新计时 搜索框输入、窗口大小调整 lodash.debounce
节流(Throttle) 固定时间间隔内最多处理一次事件 滚动事件、鼠标移动 lodash.throttle
采样(Sampling) 定期取最新值进行处理 实时数据监控 RxJS sampleTime
缓冲(Buffering) 收集多个事件后批量处理 日志记录、分析数据收集 RxJS bufferCount
丢弃(Dropping) 当处理不过来时丢弃部分事件 极高频率事件处理 自定义实现
分页(Pagination) 分批加载处理数据 大数据集展示 滚动加载实现

2.4.5 背压处理最佳实践

  1. 识别性能瓶颈:使用DevTools分析应用性能,确定是否需要背压处理
  2. 选择合适的策略:根据场景选择防抖、节流、采样等不同策略
  3. 合理设置时间参数:太短达不到效果,太长影响用户体验
  4. 考虑内存管理:对于缓冲策略,注意控制缓冲区大小
  5. 提供用户反馈:当主动丢弃数据时,应通知用户
  6. 测试极端情况:模拟高负载情况测试背压处理效果
  7. 结合Web Worker:对于CPU密集型任务,考虑使用Web Worker分担主线程压力

2.5 异步与非阻塞(Async & Non-blocking)

2.5.1 异步编程基础

异步编程是现代JavaScript和React开发的核心概念,它允许程序在等待耗时操作(如网络请求、文件I/O)完成时继续执行其他任务,而不是阻塞整个应用。

React中的常见异步场景:

  1. 数据获取(API调用)
  2. 定时操作(setTimeout/setInterval)
  3. 事件处理(用户交互、WebSocket)
  4. 动画和过渡效果
  5. 懒加载组件和代码分割

2.5.2 React异步处理机制

2.5.2.1 使用useEffect处理副作用
jsx 复制代码
function AsyncDataLoader() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      setError(null);
      try {
        // 模拟API调用
        await new Promise(resolve => setTimeout(resolve, 1000));
        const mockData = {
          userId: 1,
          id: 1,
          title: '异步加载的React数据',
          completed: false
        };
        setData(mockData);
      } catch (err) {
        setError(err.message || '请求失败');
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, []);

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error}</div>;
  if (!data) return null;

  return (
    <div>
      <h2>{data.title}</h2>
      <p>用户ID: {data.userId}</p>
    </div>
  );
}
2.5.2.2 使用useReducer管理复杂异步状态
jsx 复制代码
function asyncReducer(state, action) {
  switch (action.type) {
    case 'FETCH_START':
      return { ...state, loading: true, error: null };
    case 'FETCH_SUCCESS':
      return { loading: false, error: null, data: action.payload };
    case 'FETCH_ERROR':
      return { ...state, loading: false, error: action.payload };
    default:
      throw new Error(`未知action类型: ${action.type}`);
  }
}

function ReducerAsyncDemo() {
  const [state, dispatch] = useReducer(asyncReducer, {
    loading: false,
    error: null,
    data: null
  });

  const fetchData = useCallback(async () => {
    dispatch({ type: 'FETCH_START' });
    try {
      // 模拟API调用
      await new Promise((resolve, reject) => {
        setTimeout(() => {
          Math.random() > 0.3 
            ? resolve({
                id: 1,
                name: 'Reducer管理的数据',
                value: Math.random()
              })
            : reject(new Error('随机模拟错误'));
        }, 800);
      }).then(data => {
        dispatch({ type: 'FETCH_SUCCESS', payload: data });
      });
    } catch (error) {
      dispatch({ type: 'FETCH_ERROR', payload: error.message });
    }
  }, []);

  return (
    <div>
      <button onClick={fetchData} disabled={state.loading}>
        {state.loading ? '加载中...' : '获取数据'}
      </button>
      {state.error && <div style={{ color: 'red' }}>错误: {state.error}</div>}
      {state.data && (
        <div>
          <h3>{state.data.name}</h3>
          <p>值: {state.data.value}</p>
        </div>
      )}
    </div>
  );
}

2.5.3 高级异步模式

2.5.3.1 竞态条件处理
jsx 复制代码
function RaceConditionDemo() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  const requestRef = useRef(null);

  useEffect(() => {
    if (!query.trim()) {
      setResults([]);
      return;
    }
    
    const currentRequest = {};
    requestRef.current = currentRequest;
    
    const search = async () => {
      setLoading(true);
      try {
        // 模拟API调用
        await new Promise(resolve => 
          setTimeout(resolve, 500 + Math.random() * 1000)
        );
        
        // 检查是否是最新的请求
        if (requestRef.current !== currentRequest) return;
        
        const mockResults = Array.from({ length: 5 }, (_, i) => ({
          id: `${query}-${i}`,
          title: `${query} 结果 ${i + 1}`,
          relevance: Math.random()
        })).sort((a, b) => b.relevance - a.relevance);
        
        setResults(mockResults);
      } finally {
        if (requestRef.current === currentRequest) {
          setLoading(false);
        }
      }
    };
    
    const timer = setTimeout(search, 300); // 防抖延迟
    
    return () => {
      clearTimeout(timer);
    };
  }, [query]);

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="搜索..."
      />
      {loading && <div>搜索中...</div>}
      <ul>
        {results.map(result => (
          <li key={result.id}>{result.title}</li>
        ))}
      </ul>
    </div>
  );
}
2.5.3.2 并行与顺序请求
jsx 复制代码
function MultiRequestDemo() {
  const [userData, setUserData] = useState(null);
  const [postsData, setPostsData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [progress, setProgress] = useState(0);

  const fetchSequentially = async () => {
    setLoading(true);
    setProgress(0);
    
    try {
      // 第一个请求 - 用户数据
      const user = await fetchMockAPI('/user/1', 800);
      setUserData(user);
      setProgress(33);
      
      // 第二个请求 - 用户帖子
      const posts = await fetchMockAPI('/posts?userId=1', 600);
      setPostsData(posts);
      setProgress(66);
      
      // 第三个请求 - 用户好友
      const friends = await fetchMockAPI('/friends/1', 400);
      setProgress(100);
      
      console.log('所有数据:', { user, posts, friends });
    } catch (error) {
      console.error('请求失败:', error);
    } finally {
      setLoading(false);
    }
  };

  const fetchInParallel = async () => {
    setLoading(true);
    setProgress(0);
    
    try {
      const [user, posts, friends] = await Promise.all([
        fetchMockAPI('/user/1', 800),
        fetchMockAPI('/posts?userId=1', 600),
        fetchMockAPI('/friends/1', 400)
      ]);
      
      setUserData(user);
      setPostsData(posts);
      setProgress(100);
      console.log('所有数据:', { user, posts, friends });
    } catch (error) {
      console.error('请求失败:', error);
    } finally {
      setLoading(false);
    }
  };

  // 模拟API调用
  const fetchMockAPI = async (endpoint, delay) => {
    await new Promise(resolve => setTimeout(resolve, delay));
    return { endpoint, data: `模拟数据 ${delay}ms` };
  };

  return (
    <div>
      <h2>并行与顺序请求</h2>
      <div>
        <button onClick={fetchSequentially} disabled={loading}>
          顺序请求
        </button>
        <button onClick={fetchInParallel} disabled={loading}>
          并行请求
        </button>
      </div>
      {loading && (
        <div>
          <progress value={progress} max="100" />
          {progress}%
        </div>
      )}
      <div>
        <h3>用户数据</h3>
        <pre>{JSON.stringify(userData, null, 2)}</pre>
        <h3>帖子数据</h3>
        <pre>{JSON.stringify(postsData, null, 2)}</pre>
      </div>
    </div>
  );
}

2.5.4 非阻塞UI模式

2.5.4.1 过渡与Suspense
jsx 复制代码
import { Suspense, useState, useEffect } from 'react';

// 模拟异步资源
function createResource(promise) {
  let status = 'pending';
  let result;
  let suspender = promise.then(
    r => {
      status = 'success';
      result = r;
    },
    e => {
      status = 'error';
      result = e;
    }
  );
  
  return {
    read() {
      if (status === 'pending') throw suspender;
      if (status === 'error') throw result;
      return result;
    }
  };
}

function fetchUser(id) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        id,
        name: `用户 ${id}`,
        email: `user${id}@example.com`
      });
    }, 2000);
  });
}

function UserProfile({ resource }) {
  const user = resource.read();
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>Email: {user.email}</p>
    </div>
  );
}

function SuspenseDemo() {
  const [userId, setUserId] = useState(1);
  const [resource, setResource] = useState(createResource(fetchUser(1)));
  
  const handleChange = e => {
    const newId = parseInt(e.target.value);
    setUserId(newId);
    setResource(createResource(fetchUser(newId)));
  };

  return (
    <div>
      <div>
        <label>
          选择用户ID:
          <select value={userId} onChange={handleChange}>
            {[1, 2, 3, 4, 5].map(id => (
              <option key={id} value={id}>{id}</option>
            ))}
          </select>
        </label>
      </div>
      <Suspense fallback={<div>加载用户数据...</div>}>
        <UserProfile resource={resource} />
      </Suspense>
    </div>
  );
}
2.5.4.2 使用useTransition优化用户体验
jsx 复制代码
function TransitionDemo() {
  const [resource, setResource] = useState(createResource(fetchUser(1)));
  const [isPending, startTransition] = useTransition();
  const [userId, setUserId] = useState(1);

  const handleChange = e => {
    const newId = parseInt(e.target.value);
    setUserId(newId);
    
    // 使用startTransition标记为非紧急更新
    startTransition(() => {
      setResource(createResource(fetchUser(newId)));
    });
  };

  return (
    <div>
      <div>
        <label>
          选择用户ID:
          <select value={userId} onChange={handleChange}>
            {[1, 2, 3, 4, 5].map(id => (
              <option key={id} value={id}>{id}</option>
            ))}
          </select>
        </label>
        {isPending && <span style={{ marginLeft: '10px' }}>加载中...</span>}
      </div>
      <Suspense fallback={<div>加载用户数据...</div>}>
        <UserProfile resource={resource} />
      </Suspense>
    </div>
  );
}

2.5.5 异步最佳实践

  1. 错误处理:始终处理Promise拒绝情况,避免未捕获的Promise
  2. 取消机制:为长时间运行的异步操作实现取消功能
  3. 加载状态:提供清晰的加载状态反馈
  4. 竞态条件防护:使用ref或取消token防止过时响应
  5. 资源清理:在组件卸载时清理异步操作
  6. 性能优化:合理使用并行请求和懒加载
  7. 用户体验:考虑使用骨架屏(Skeleton)等优化技术
  8. 测试策略:编写全面的测试覆盖各种异步场景

总结

React的异步与非阻塞编程模型是现代前端开发的核心。通过合理运用Promise、async/await、Suspense等特性,结合useEffect、useReducer等Hooks,开发者可以构建响应迅速、用户体验良好的应用程序。关键在于理解JavaScript的事件循环机制和React的渲染周期,从而避免常见的性能陷阱和竞态条件问题。随着React并发模式的不断发展,异步处理能力将变得更加强大和灵活。

相关推荐
山青花欲燃3104 分钟前
React 对接流式接口实现
前端·llm
LaoZhangAI6 分钟前
【2025最新】Gemini 2.5 Pro完全指南:强大推理能力提升8大应用场景实战效率
前端
平山13 分钟前
浅析JavaScript的内存机制
javascript·面试
frontDeveloper15 分钟前
JavaScript基础知识概览(非DOM-API部分)
javascript
晓风伴月16 分钟前
Css:如何解决绝对定位子元素内容被父级元素overflow:hidden属性剪裁
前端·css·overflow裁剪
Carlos_sam17 分钟前
OpenLayers:海量图形渲染之矢量切片
前端·javascript
Nexmoe18 分钟前
20 万行代码:我们如何构建和维护大规模 AI 原型系统
前端
zhangxingchao18 分钟前
Andrdoid中常用的JVM知识整理
前端
海底火旺18 分钟前
聊一聊JavaScript中的立即执行函数(IIFE)
前端·javascript
76756047918 分钟前
useMemoize 方法源码解读与实战应用
前端·源码