React Redux选择数组时避免多余重渲染的正确方法是什么?
你遇到的问题本质是默认Reselect选择器使用引用相等判断依赖是否变化,只要state.items的引用发生变更(比如修改其中某个元素的非id属性时的不可变更新),就会触发selectIds重新计算,返回新的数组引用,进而触发组件重渲染。
方案1:自定义带id感知的Reselect选择器(最推荐)
Reselect支持通过createSelectorCreator自定义选择器的依赖比较规则,你可以把依赖items的比较逻辑从默认的引用相等,改为只判断items的id序列是否变化,只有id真的变化时才重新计算id数组:
- 编写id比较函数
// 比较两个items数组的id序列是否完全一致 const isItemsIdEqual = (prevItems: Item[], nextItems: Item[]) => { if (prevItems === nextItems) return true if (prevItems.length !== nextItems.length) return false for (let i = 0; i < prevItems.length; i++) { if (prevItems[i].id !== nextItems[i].id) return false } return true }
- 创建自定义选择器构造器
import { createSelectorCreator, defaultMemoize } from 'reselect' // 构造专门处理items数组的选择器,用id相等判断依赖是否变化 const createIdAwareSelector = createSelectorCreator( defaultMemoize, isItemsIdEqual )
- 重写
selectIds选择器
const selectItems = (state: MyStateType) => state.items const selectIds = createIdAwareSelector( selectItems, (items) => items.map(item => item.id) )
改造后,只有当items发生增删、排序、id修改时,才会触发selectIds重新计算,返回新的id数组;如果只是修改某个元素的其他属性,selectIds会直接返回之前缓存的数组引用,useSelector默认的引用相等校验就会认为值没有变化,不会触发组件重渲染。且id比较逻辑只会在Redux状态变更时执行一次,全局共享缓存,比在useSelector中传自定义比较函数的性能高很多。
改造后无需在
useSelector中额外传入自定义比较函数,默认的引用相等校验就够用。
方案2:Reducer层单独维护id列表(极致性能场景)
如果你的items数组量级特别大(几千条以上),且dispatch调用非常频繁,可以直接在Reducer中额外维护一个allIds字段,专门存储所有item的id:
// state结构 state = { items: [{id: 1, keys...}, {id: 2, keys...}, ...], allIds: [1,2,3,...] }
只有当执行增删item、修改id、调整顺序的操作时,才更新allIds的引用,修改item其他属性时不需要改动allIds。此时选择器可以直接取state.allIds,完全不需要计算,性能最高。缺点是需要维护两份关联数据,更新时要保证两者同步。
内容的提问来源于stack exchange,提问作者newimprovement

