You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用EventSource向Redux存数据时页面冻结问题排查与解决

差异原因分析

  1. EventSource的同步阻塞特性:EventSource的onmessage回调是直接在主线程同步执行的。当服务器每秒推送10条消息时,这些回调会几乎无间隔地抢占主线程,每次触发两次Redux dispatch。随着store中数据量增长,reducer计算、组件重渲染的开销持续累加,主线程被完全占用,无法及时处理UI渲染、用户交互等任务,最终导致页面冻结。
  2. 异步调度的间隙差异:setInterval+fetch模式中,fetch的响应回调属于微任务,浏览器会在当前宏任务结束后、下一次UI渲染前处理微任务;且setInterval的100ms间隔会给主线程留出喘息时间,让浏览器能在两次请求间隙完成渲染和其他任务。而EventSource的消息是独立宏任务,连续推送的消息会让主线程被dispatch和重渲染占满,没有间隙处理UI。
  3. 高频单条更新的重渲染开销:每次单条数据的dispatch都会触发订阅组件重渲染。EventSource每秒10次的高频单条更新,会导致组件每秒重渲染10次,随着数据量增大,每次重渲染的DOM对比、更新成本越来越高;而setInterval的间隔让浏览器有机会合并部分渲染任务,所以能支撑更大的数据量。

解决方案

1. 批量处理EventSource消息

短时间内收集推送的消息,再一次性dispatch到Redux,减少dispatch次数和重渲染频率:

useEffect(() => {
    const eventSource = new EventSource('http://localhost:3000/events');
    let messageBatch = [];
    // 每200ms批量处理一次,可根据业务调整间隔
    const batchTimer = setInterval(() => {
        if (messageBatch.length === 0) return;
        // 批量分发数据
        dispatch(addMapDataBatch(
            messageBatch.map(item => ({
                DefectX: item.DefectPosX,
                DefectY: item.DefectPosY,
                DefectArea: item.DefectArea,
            }))
        ));
        dispatch(changeDefectListBatch(messageBatch));
        messageBatch = [];
    }, 200);

    eventSource.onmessage = (event) => {
        const newMessage = JSON.parse(event.data);
        messageBatch.push(newMessage);
    };

    eventSource.onerror = (err) => {
        console.error('EventSource failed:', err);
        clearInterval(batchTimer);
        eventSource.close();
    };

    return () => {
        clearInterval(batchTimer);
        eventSource.close();
    };
}, []);

需对应在Redux reducer中新增批量处理的action,一次性处理数组数据,避免逐个处理的额外开销。

2. 优化组件重渲染

  • 用React.memo包裹订阅Redux store的组件,避免props未变化时的无意义重渲染;
  • 在组件内部用useMemo缓存基于store数据生成的DOM列表等计算结果;
  • 用useCallback缓存传递给子组件的函数,避免因函数引用变化触发子组件重渲染。

3. 限制store数据量

如果不需要保留全量历史数据,可设置数据上限,采用滚动窗口淘汰旧数据,减少每次处理的数据规模:

// 在changeDefectListBatch的reducer逻辑中
case 'CHANGE_DEFECT_LIST_BATCH':
    return {
        ...state,
        // 只保留最新10000条数据,可按需调整
        defectList: [...state.defectList, ...action.payload].slice(-10000)
    };

4. 合并单次消息中的多dispatch

使用Redux的batchAPI(Redux Toolkit默认启用,手动使用需导入),将单条消息中的两次dispatch合并为一次更新,减少重渲染次数:

import { batch } from 'react-redux';

// 修改EventSource的onmessage回调
eventSource.onmessage = (event) => {
    const newMessage = JSON.parse(event.data);
    batch(() => {
        dispatch(
            addMapData({
                DefectX: newMessage.DefectPosX,
                DefectY: newMessage.DefectPosY,
                DefectArea: newMessage.DefectArea,
            })
        );
        dispatch(changeDefectList(newMessage));
    });
};

内容的提问来源于stack exchange,提问作者Ali KARABAY

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 17:43:14