react-redux 扁平化树状态更新触发所有节点组件强制重渲染问题排查
问题根因分析
核心原因
- 全量替换store中的tree对象,所有节点引用失效
你每次触发树更新时,都会通过Tree.toFlatNodes生成全新的整个扁平化树对象,再通过SET_TREEreducer直接替换store中的整个tree字段。哪怕只有单个节点的属性发生变化,整个tree对象、以及tree下所有节点的引用都会被更新,就算某个未修改节点的height、childIds、data.type的实际值和之前完全一致,对应字段的引用已经和旧状态完全不同。 - 浅比较逻辑触发所有节点重渲染
你的selectNodeSeed每次调用都会返回一个新对象,虽然你给useSelector传入了shallowEqual做对比,但shallowEqual只会对比返回对象第一层属性的引用:- 未修改节点的
childIds数组因为全量替换tree,引用已经更新,浅比较会判定该字段变化 - 如果节点的
data对象引用更新,就算data.type值不变,也不会影响基础类型的dataType判断,但如果后续修改为返回引用类型字段也会被判定变化
只要返回对象的任意一个第一层属性引用变化,shallowEqual就会返回false,触发对应节点组件重渲染。
- 未修改节点的
额外笔误提示:你组件里解构的字段是
etlUnitType,但选择器返回的对应字段是dataType,不会影响重渲染逻辑,但会导致取值异常。
可行解决方案
- 优化树更新逻辑,采用Immutable更新方式:只修改发生变化的节点以及其上游链路节点的引用,未变动的节点完全复用原有引用,保证未修改节点的
childIds等字段引用不变。 - 用
reselect库创建带缓存的参数化选择器,给每个节点id单独做缓存,只有对应节点的相关属性实际发生变化时才返回新的结果对象。 - 拆分选择器逻辑,单个属性单独调用
useSelector获取:比如分别写selectNodeHeight、selectNodeChildIds、selectNodeDataType三个选择器,分别调用useSelector取值,只要单个属性的引用/值没有变化就不会触发重渲染。 - 极端场景下可以把
shallowEqual换成深比较函数,但仅推荐树节点数量少的场景使用,节点量大时深比较会带来额外性能开销。
内容的提问来源于stack exchange,提问作者Edmund's Echo
相关产品推荐
相关产品推荐

