Redux dispatch耗时远超Reducer,为何出现此差异?
我打开一个文件,解析后将其dispatch到Redux状态中。对dispatch进行计时发现耗时远超预期,似乎dispatch本身比reducer要多花费数百毫秒。
打开并解析文件后,调用了以下dispatch代码,batch用于计时功能:
batch(() => { const t = performance.now(); dispatch( setNewOpenFile({ filePath, unpackedData, }) ); console.log('Time for dispatch: ', (performance.now() - t) + " ms"); })
在setNewOpenFile reducer内部:
setNewOpenFile: ( state, action: PayloadAction<{ filePath: string; unpackedData: ParsedFile; }> ) => { const t0 = performance.now(); const { unpackedData, filePath } = action.payload; const t1 = performance.now(); const fileContent = convertFile(unpackedData); console.log("Time to convert file: " + (performance.now() - t1) + " ms"); ... const newFile: IOpenFile = { id: uuid(), fileContent, ... }; state.openFiles[newFile.id] = newFile; console.log("Time to open file: " + (performance.now() - t0) + " ms"); },
测试结果与预期不符:
- Time for dispatch: 400-450 ms
- Time to open file: 100-120 ms
- Time to convert file: 85-90 ms
多次运行结果稳定。
请问此现象是否正常?若正常,dispatch比reducer多花费300ms的原因可能是什么?
这种现象不正常,Redux的dispatch本身是同步执行的核心逻辑,理论上额外耗时应该极短,不会出现几百毫秒的差距。以下是可能导致dispatch总耗时远高于reducer的核心原因:
1. React组件重渲染的开销
Redux状态更新后,所有订阅了该状态的React组件都会触发重渲染。你在batch内部的计时,实际上把reducer执行 + 所有相关组件重渲染的总时间都算进了dispatch的耗时里,而reducer内部的计时只统计了自身逻辑的执行时间:
- 当
openFiles状态更新后,大量关联组件(比如文件列表、编辑器面板、侧边栏等)可能会重新渲染 - 如果这些组件结构复杂、渲染的数据量较大,重渲染的总耗时会远超过reducer本身的执行时间
2. Redux中间件的额外执行
如果你的Redux store配置了多个中间件(比如日志中间件、持久化中间件、自定义中间件等),dispatch会依次触发所有中间件的逻辑:
- 比如持久化中间件可能会同步将新状态写入localStorage/IndexedDB,这类IO操作会占用大量时间
- 日志中间件如果要序列化大体积的状态对象,也会产生可观的耗时
3. batch函数的逻辑影响
你使用的batch函数(通常来自React-Redux或Redux Toolkit),其作用是合并组件重渲染以优化性能,但你的计时范围包含了batch内部的完整流程,可能涵盖了batch调度重渲染的额外逻辑,导致计时结果包含了更多非reducer的操作。
4. 状态数据体积过大
如果unpackedData或转换后的fileContent体积非常大:
- 在dispatch传递action时,大体积数据的复制、序列化会产生额外开销
- 若Redux DevTools处于开启状态,它会在dispatch后同步生成状态快照,对大状态树的序列化操作会非常耗时
验证排查方法
- 暂时关闭Redux DevTools,观察dispatch耗时是否下降,排除DevTools的影响
- 使用React DevTools的「Profiler」面板录制状态更新过程,查看哪些组件重渲染耗时最长
- 临时注释掉所有订阅
openFiles状态的组件,仅保留基础状态更新逻辑,对比dispatch耗时变化 - 逐个移除Redux store的中间件,测试每次移除后的耗时情况,定位耗时来源
内容的提问来源于stack exchange,提问作者1jss

