React / Redux 中树结构和 shouldComponentUpdate 的性能问题

IT技术 reactjs redux immutable.js
2021-04-28 02:35:28

我对 React、Redux 和 ImmutableJS 相当陌生,并且遇到了一些性能问题。

我有一个大的数据树结构,我目前在这个结构中存储为一个平面列表:

new Map({
  1: new Node({
    id: 1,
    text: 'Root',
    children: [2,3]
  }),
  2: new Node({
    id: 2,
    text: 'Child 1',
    children: [4]
  }),
  3: new Node({
    id: 3,
    text: 'Child 2',
    children: []
  }),
  4: new Node({
    id: 4,
    text: 'Child of child 1',
    children: []
  })
});

虽然将其构建为平面列表使更新节点变得容易,但我发现随着树的增长,交互变得缓慢。交互包括能够选择一个或多个节点、切换其子节点的可见性、更新文本等。看起来 UI 缓慢的一个关键原因是每次交互都重新绘制整个树。

我想使用shouldComponentUpdate这样的方式,如果我更新节点 3,节点 2 和 4 不会更新。如果将数据存储为树(我可以简单地检查是否this.props !== nextProps),这将很容易,但是由于数据存储在平面列表中,因此检查将变得更加复杂。

我应该如何存储数据并使用shouldComponentUpdate(或其他方法)来支持具有数百或数千个树节点的流畅 UI?

编辑

我一直在顶级连接商店,然后不得不将整个商店传递给子组件。

我的结构是:

<NodeTree> 
  <NodeBranch>
    <Node>{{text}}</Node>
    <NodeTree>...</NodeTree>
  </NodeBranch>
  <NodeBranch>
    <Node>{{text}}</Node>
    <NodeTree>...</NodeTree>
  </NodeBranch>
  ...
</NodeTree>

<Node>可以做一个简单的检查以shouldComponentUpdate查看是否标题已经改变,但我没有类似的解决方案,以使用上<NodeTree><NodeBranch>给出树的递归特性。

看起来最好的解决方案(感谢@Dan Abramov)是连接每个<NodeBranch>,而只是在顶层连接。今晚我将对此进行测试。

2个回答

刚刚添加了一个新示例来说明这一点。
你可以像这样运行它:

git clone https://github.com/rackt/redux.git

cd redux/examples/tree-view
npm install
npm start

open http://localhost:3000/

Dan Abramov 解决方案在 99% 的常见情况下都很好。

但是每个增量所需的计算时间与树中的节点数成正比O(n),因为每个连接的节点 HOC 都必须执行一点代码(如身份比较......)。然而,这段代码执行起来非常快。

如果您测试 Dan Abramov 解决方案,我的笔记本电脑上有 10k 个节点,当我尝试增加计数器时,我开始看到一些延迟(例如 100 毫秒延迟)。在廉价的移动设备上,情况可能更糟。

有人可能会争辩说,您不应该尝试一次在 DOM 中渲染 10k 个项目,我完全同意这一点,但是如果您真的想显示很多项目并将它们连接起来,那就不是那么简单了。

如果您想将此O(n) 转换O(1),那么您可以实现自己的connect系统,其中 HOC(高阶组件)订阅仅在底层计数器更新时触发,而不是所有 HOC 的订阅。

我已经在这个答案中介绍了如何做到这一点

在 react-redux 上创建了一个问题,以便connect变得更加灵活,并允许通过选项轻松自定义商店的订阅。这个想法是您应该能够重用connect代码并有效地订阅状态切片更改而不是全局状态更改(即store.subscribe())。

const mapStateToProps = (state,props) => {node: selectNodeById(state,props.nodeId)}

const connectOptions = {
   doSubscribe: (store,props) => store.subscribeNode(props.nodeId)
}

connect(mapStateToProps, undefined,connectOptions)(ComponentToConnect)

使用subscribeNode方法创建商店增强器仍然是您自己的责任