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

Posted

技术标签:

【中文标题】React / Redux 中树结构和 shouldComponentUpdate 的性能问题【英文标题】:Performance issues with a tree structure and shouldComponentUpdate in React / Redux 【发布时间】:2016-05-01 03:10:20 【问题描述】:

我对 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>

&lt;Node&gt; 可以使用 shouldComponentUpdate 进行简单检查,以查看标题是否已更改,但鉴于树的递归性质,我没有类似的解决方案可用于 &lt;NodeTree&gt;&lt;NodeBranch&gt;

看起来最好的解决方案(感谢@Dan Abramov)是连接每个&lt;NodeBranch&gt;,而不是仅在顶层连接。今天晚上我将进行测试。

【问题讨论】:

shouldComponentUpdate 与您的 React 组件是否应该重新渲染有关,而不是与更新/编辑存储中的数据的逻辑有关。向我们展示您用于更新商店的代码。 如何将数据从存储传递到树组件?你的树组件是如何构成的——它是一个单一的组件,是嵌套的组件,嵌套的连接组件......? 【参考方案1】:

我just added 是一个新的例子。 你可以这样运行它:

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

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

open http://localhost:3000/

【讨论】:

非常感谢这个例子——我读过github.com/rackt/redux/issues/815,但不太了解它在实践中是如何工作的。我今晚会试一试,希望它能解决问题:) 这已经充分解决了我的MVP问题,所以接受这个解决方案。很高兴在 GitHub 上也看到很多积极的反馈。谢谢丹 :) 链接是404ing。 Is this the link now? 感谢您进行回购,我认为@NickL 建议的链接是正确的,但最好在这里也看到对整个过程的描述,以避免仅链接的答案. 我相信这是链接,github.com/reduxjs/redux/pull/1269【参考方案2】:

Dan Abramov 解决方案适用于 99% 的常见情况。

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

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

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

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

我已经在answer 中介绍了如何做到这一点。

我在 react-redux 上创建了一个issue,这样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 方法创建商店增强器仍然是您自己的责任。

【讨论】:

感谢您的信息。只是确认 HOC 意味着高阶组件?谢谢。 未来我们可能会进入数千个节点的领域,随着我们的树在水平和垂直方向上扩展,无穷大的解决方案将变得很棘手!由于使用 React 的时间有限,我很难完全理解您提出的解决方案,因此很想了解您如何将自定义连接应用到 Dan 的解决方案。 @PaddyMann 我会尝试提供解决方案。我想先对 react-redux 做一个 PR,这样就可以很容易地做到,而不必复制大部分 react-redux 的连接代码。 太棒了@SebastienLorber。想在 GitHub 上创建一个问题,以便我/其他人可以跟踪进度吗? (我问你可能会更好地解释这个问题,我不想夸大你的意图)

以上是关于React / Redux 中树结构和 shouldComponentUpdate 的性能问题的主要内容,如果未能解决你的问题,请参考以下文章

React-native + Redux(Combine Reducers):创建状态结构

React Native中树 TreeView 实现

React+Redux 项目的标准文件夹结构是啥?

180React15.6.0ReactDOM15.6.0和React-Redux.4.0.0.js源码结构

React 学习笔记总结

React 学习笔记总结