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

在这里插入图片描述


在这里插入图片描述

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 自定义事件总线实现
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场景:

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模式:

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 多主题复杂订阅
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
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)
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提供了多种背压策略操作符:

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 分页加载大数据集
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高频数据处理
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处理副作用
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管理复杂异步状态
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 竞态条件处理
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 并行与顺序请求
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
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优化用户体验
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并发模式的不断发展,异步处理能力将变得更加强大和灵活。

评论 559
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百锦再@新空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值