Redux createSelector跨组件记忆缓存与useMemo使用疑问
首先明确createSelector生成的选择器的缓存规则:
默认情况下,这类选择器只会缓存最近一次调用的结果:连续两次调用时,只有所有传入参数做浅比较完全相等,才会直接返回缓存值;只要任意参数和上一次不一致,缓存就直接失效,会重新执行计算逻辑,同时把新参数和计算结果存为新的缓存条目。
你测试时看到缓存正常工作只是巧合:两个Sum组件传的value都是1000,调用同一个全局选择器时入参完全一致,自然能命中缓存。你可以改下测试代码:把第二个Sum的value改成2000,再点按钮触发更新,就会看到控制台每次都打印两次输出计算的日志——这就是缓存被反复覆盖失效的表现。
全局单选择器的缓存失效逻辑
如果多个组件复用同一个在全局定义的选择器,且各自传入的自定义参数(即示例里的n值)不一样,调用流程会出现持续的缓存失效:
- 第一次渲染,传1000的组件A先调用选择器:入参和初始空缓存不匹配,执行计算,缓存存为「state+1000 → A的求和结果」
- 紧接着传2000的组件B调用同一个选择器:入参n从1000变成2000,缓存失效,重新计算,缓存被覆盖为「state+2000 → B的求和结果」
- 下一次state更新触发重渲染,组件A再次调用选择器:入参n从缓存里的2000变回1000,缓存又失效,重算后缓存又换回1000对应的结果
- 组件B紧接着调用,n又变成2000,缓存再失效重算
这种场景下选择器永远接收不到连续两次相同的入参,记忆缓存完全成了摆设,每次调用都要重跑计算逻辑,createSelector的性能优化效果直接为零。
useMemo的作用
解决这个问题的核心思路很简单:给每个组件实例单独创建一个属于自己的选择器实例,每个选择器自己维护自己的缓存,不会被其他组件的调用冲掉。useMemo的作用就是在当前组件的整个生命周期里,把这个专属选择器实例缓存住,避免每次组件重渲染都生成一个全新的选择器——新创建的选择器缓存是空的,自然也起不到记忆作用。
正确写法参考:
function Sum({ value }) { const dispatch = useDispatch(); // 仅在组件初始化时创建一次专属选择器,整个生命周期复用 const selectPrivateSum = useMemo(() => createSelector( [selectX, selectY, (_, n) => n], (x, y, num) => { console.log("output selector called: " + num); return x + y + num; } ), []); // 调用自己的专属选择器,缓存不受其他组件影响 const { x, y, sum } = useSelector(state => ({ x: state.x, y: state.y, sum: selectPrivateSum(state, value) })); return ( <div> <span>({x},{y})</span> <button onClick={() => dispatch(incrementX(1))}>X+1</button> <button onClick={() => dispatch(incrementY(1))}>Y+1</button> <br /> <span>sum: {sum}</span> </div> ); }
按这个写法改完,哪怕两个组件传的value分别是1000和2000,各自的选择器也会独立维护缓存,只要自己依赖的x、y、当前value没变,就不会重复执行计算逻辑。
内容的提问来源于stack exchange,提问作者Chong Lip Phang

