重构Redux Reselect选择器以优化Memoization性能
我完全懂你的痛点——现在的选择器大概率会在公民详情更新时也重新计算总数,这纯粹是浪费性能。咱们用Reselect的核心特性,精准控制选择器的重新计算时机,只在公民条目新增/移除或者大洲关联的国家列表变化时才更新总数。
问题根源
当前的选择器可能直接依赖了包含完整公民对象的state分支,或者依赖的输入选择器没有正确做memoization。当公民详情(比如姓名、年龄)更新时,即使总数不变,选择器也会因为依赖的对象引用变化而重新运行。
重构方案
我们的目标是让选择器只依赖和总数直接相关的最小数据集合,并且确保这些依赖只有在必要时才变化。
1. 拆分并优化输入选择器
首先定义几个精准的输入选择器,只获取计算总数必需的数据:
import { createSelector } from 'reselect'; // 输入选择器:获取按大洲分组的国家ID列表(确保这个选择器是纯函数) const getCountryIdsByContinent = (state) => state.continents.countryIdsByContinent; // 输入选择器:获取原始公民数组(仅作为底层依赖) const getCitizens = (state) => state.citizens; // 关键:memoized的输入选择器,仅在公民新增/移除时更新(详情更新不影响) const getCitizenCountryIds = createSelector( [getCitizens], (citizens) => citizens.map(citizen => citizen.countryId) );
这里getCitizenCountryIds会被Reselect自动memoize,只有当state.citizens的引用发生变化(新增/移除公民)时,才会重新生成countryId数组;公民详情更新时,state.citizens的引用不变,选择器会直接返回缓存的旧数组。
2. 创建参数化的总数选择器
接下来用createSelector组合这些输入选择器,同时支持传入continent参数来获取指定大洲的公民总数:
// 参数化选择器:接受continent作为第二个参数 export const getCitizensNumberByContinentSelector = createSelector( [ getCountryIdsByContinent, getCitizenCountryIds, (state, continent) => continent // 将continent作为输入依赖的一部分 ], (countryIdsByContinent, citizenCountryIds, continent) => { // 获取指定大洲对应的所有国家ID const targetCountryIds = countryIdsByContinent[continent] || []; // 统计属于这些国家的公民数量 return citizenCountryIds.filter(countryId => targetCountryIds.includes(countryId)).length; } );
3. 使用方式
在组件中使用时,直接传入continent参数即可:
// 在组件的useSelector中调用 const citizenCount = useSelector(state => getCitizensNumberByContinentSelector(state, 'Europe'));
为什么这能解决问题?
Reselect的createSelector会对每个输入依赖的返回值做浅比较,只有当至少一个依赖的返回值发生变化时,才会重新运行计算函数:
- 当公民详情更新时:
state.citizens的引用不变,getCitizenCountryIds返回缓存的旧数组,选择器不会重新计算。 - 当新增/移除公民时:
state.citizens的引用变化,getCitizenCountryIds生成新数组,选择器重新计算总数。 - 当大洲关联的国家列表变化时:
getCountryIdsByContinent的返回值变化,选择器重新计算总数。
额外优化建议
如果你的state已经维护了citizensById(以ID为键的公民对象映射)或者citizenCountryIds的独立分支,直接使用这些数据会更高效,避免每次调用map生成新数组。比如:
// 如果state里已有citizenCountryIds分支 const getCitizenCountryIds = (state) => state.citizenCountryIds;
这样可以省去一层memoization,进一步提升性能。
内容的提问来源于stack exchange,提问作者Zosimas

