Redux中多状态值相等性校验问题:useSelector解构写法失效原因及基于Lodash isEqual的优化方案
问题原因分析
你的解构写法失效的核心问题,在于你选择了整个testResultsTreeReducer的状态对象,而不是只提取你需要的三个属性。这里有两个关键问题:
不必要的重渲染触发:如果
testResultsTreeReducer里还有其他你不关心的属性,只要那些属性发生变化,Redux reducer就会返回一个全新的状态对象。此时isEqual会对比前后两次的完整reducer对象,发现内容不同,就会强制组件重新渲染——哪怕你需要的三个属性根本没变化。潜在的状态更新遗漏(如果reducer写法不规范):要是你的reducer在更新状态时没有返回新对象(比如直接修改原state后返回),那么
state.testResultsTreeReducer的引用不会变。哪怕你需要的三个属性值已经更新,isEqual对比相同引用会返回true,导致useSelector不会通知组件更新,你就看不到最新的状态。
而单独调用useSelector获取每个属性时,你只监听了特定属性的变化,所以只有当目标属性更新时才会触发组件渲染,自然能正常工作。
解决方案
下面提供两种方案,既能保留解构式的简洁写法,又能避免重复调用useSelector,同时配合isEqual(或更高效的记忆化方式)做状态校验:
方案1:自定义选择器函数 + isEqual
你可以封装一个选择器函数,专门提取你需要的三个属性,然后在useSelector中使用这个函数:
// 封装复用的选择器,只提取需要的属性 const getTestResultsTreeProps = state => ({ selectedSpectrums: state.testResultsTreeReducer.selectedSpectrums, fetchedSpectraData: state.testResultsTreeReducer.fetchedSpectraData, fetchedSpectraIndex: state.testResultsTreeReducer.fetchedSpectraIndex }); // 组件中使用 const {selectedSpectrums, fetchedSpectraData, fetchedSpectraIndex} = useSelector(getTestResultsTreeProps, isEqual);
这个写法的原理是:每次useSelector执行时,虽然会返回一个新的对象,但isEqual会对比对象的内容而非引用。只要三个属性的值都没变,isEqual就会返回true,组件不会重渲染;只有当至少一个属性值变化时,才会触发更新。
方案2:使用Redux Toolkit的createSelector(推荐)
如果你用的是Redux Toolkit(现在推荐的Redux写法),可以用它内置的createSelector创建记忆化选择器,性能更优:
import { createSelector } from '@reduxjs/toolkit'; // 基础选择器:获取整个reducer状态 const selectTestResultsTree = state => state.testResultsTreeReducer; // 记忆化选择器:只提取需要的三个属性,只有当依赖变化时才会重新计算 const selectTestResultsProps = createSelector( [selectTestResultsTree], (testResultsTree) => ({ selectedSpectrums: testResultsTree.selectedSpectrums, fetchedSpectraData: testResultsTree.fetchedSpectraData, fetchedSpectraIndex: testResultsTree.fetchedSpectraIndex }) ); // 组件中使用,无需额外传isEqual const {selectedSpectrums, fetchedSpectraData, fetchedSpectraIndex} = useSelector(selectTestResultsProps);
createSelector会缓存选择器的结果:只有当你关心的三个属性发生变化时,才会返回新的对象引用;否则直接返回缓存的旧引用。这样useSelector可以直接对比引用,不需要额外的isEqual,性能更好,代码也更整洁。
内容的提问来源于stack exchange,提问作者deathfry

