React-Redux-Reselect应用优化:Day组件不必要重渲染排查
听起来你遇到的核心问题是:明明用了Reselect,但更新单个计划时所有Day组件都重渲染,完全没达到预期的性能优化效果。我来帮你拆解下可能的原因,以及对应的解决方案:
一、核心问题分析
1. 选择器返回的数据粒度太大,未结合Day组件的日期过滤
你的makeGetRelevantObjectPlans选择器只筛选了和当前用户/项目/类型相关的所有计划,但没有过滤当前Day组件对应的日期。这意味着每个Day组件拿到的是该用户的全部计划,而不是当天的计划。
只要该用户的任何一个计划变化(哪怕是其他日期的),Day组件的planifications props就会变成新数组(因为lodash的filter每次都会返回新数组),React浅比较会认为props变化,触发重渲染。
2. Reselect缓存被无效触发
你的选择器依赖了getAllPlans(整个planifications对象),而每次更新单个计划时,reducer都会返回一个新的planifications对象(不可变更新的正确操作,但导致引用变化)。这会让所有依赖getAllPlans的选择器实例都重新计算,哪怕当前Day组件的计划根本没变化——因为Reselect的缓存是基于输入依赖的引用是否变化,而不是内容。
3. 选择器结果的引用不稳定
即使筛选后的计划内容和之前完全一致,_.filter也会返回一个新数组。React的浅比较会认为planifications props变了,从而触发重渲染,哪怕组件根本不需要更新。
二、针对性解决方案
方案1:让选择器筛选出当前Day的计划(最优先做)
首先修改选择器,加入Day组件的日期props,只返回当天的相关计划,从根源减少不必要的数据传递:
// 新增选择器:获取Day组件的日期props export const getDayPropsDate = (state, props) => props.date; // 修改makeGetRelevantObjectPlans,加入日期过滤 export const makeGetRelevantObjectPlans = () => { return createSelector( [getAllPlans, getGroup, getUserPropsId, getDayPropsDate], (planifications, group, user, date) => { switch (group.code) { case 'project': return _.filter(planifications, { projectId: user, planDate: date }); case 'user': return _.filter(planifications, { userId: user, planDate: date }); case 'type': return _.filter(planifications, { typeId: user, planDate: date }); default: return []; } } ); };
这样,只有当当天的计划发生变化时,Day组件的planifications才会更新,其他日期的Day组件不会因为非当天计划的变化而触发重渲染。
方案2:用深比较缓存选择器结果,避免无意义的新数组
为了让选择器在内容不变时返回同一个数组引用,你可以用Reselect的自定义选择器创建器,结合深比较来缓存结果:
import { createSelectorCreator, defaultMemoize } from 'reselect'; import isEqual from 'lodash/isEqual'; // 创建支持深比较的选择器创建器 const createDeepEqualSelector = createSelectorCreator( defaultMemoize, isEqual ); // 修改选择器使用createDeepEqualSelector export const makeGetRelevantObjectPlans = () => { return createDeepEqualSelector( [getAllPlans, getGroup, getUserPropsId, getDayPropsDate], (planifications, group, user, date) => { switch (group.code) { case 'project': return _.filter(planifications, { projectId: user, planDate: date }); case 'user': return _.filter(planifications, { userId: user, planDate: date }); case 'type': return _.filter(planifications, { typeId: user, planDate: date }); default: return []; } } ); };
这个方案会在选择器重新计算时,对比新结果和旧结果的内容:如果内容完全一致,就返回旧数组的引用,React浅比较会认为props没变化,不会触发重渲染。
方案3:优化Redux Store结构(长期性能优化)
如果你的数据量持续增长,可以考虑按日期分组存储计划,比如:
// 调整后的store结构 planifie: { // ...其他数据 planificationsByDate: { "2019-05-16": [plan549, planXXX], "2019-05-17": [plan550, planYYY] } }
然后修改选择器直接获取对应日期的计划列表,这样更新计划时只需要修改对应日期的数组,其他日期的数组引用不变,依赖这些数组的选择器不会重新计算,从根源避免无效重渲染。对应的reducer也需要调整,比如更新计划日期时,从旧日期数组移除,添加到新日期数组。
三、额外验证步骤
- 用
React.memo包裹Day组件,确保组件只会在props真正变化时重渲染:
const Day = React.memo((props) => { // 组件逻辑 });
- 在
componentDidUpdate中打印prevProps.planifications === props.planifications,验证引用是否稳定——如果返回true,说明缓存生效了。
内容的提问来源于stack exchange,提问作者Alicia

