【问题标题】:ReactJS sluggish with frequent updates to big DOM?ReactJS 因频繁更新大型 DOM 而变得迟缓?
【发布时间】:2015-01-18 20:25:47
【问题描述】:

我们正在考虑将 React 用于一个 DOM 繁重的项目,并想弄清楚虚拟 DOM 渲染方法的性能特征。

我担心的一件事是,虚拟 DOM 会在每次微小的状态更改时重新计算。我可以看到这个模型的好处,但是在一个包含大量元素和频繁小更新的应用程序中,这会导致像“悬停”效果这样简单的东西的大量开销。

例如,这会呈现一系列 N div 并更改 CSS 类 onMouseOver。在我的系统上,它从 N=5000 (http://jsfiddle.net/estolua/aopvp7zp/) 开始变得相当缓慢。

var D = React.DOM;

function generateItems(N) {
  return _.map(
    _.range(0, N), function (i) { return { id: '_' + i, content: '' + i }; }
  );
}

function toggle(x, y) { return (y && x!==y) ? y : null; }

var Item = React.createClass({
  render: function () {
    var item = this.props.item,
        appS = this.props.appState,
        focF = this.props.focF;
    return D.div({
        className: 'item' + (appS.focused === item.id ? ' focused' : ''),
        onMouseOver: focF
      },
      item.content
    );
  }
});

var App = React.createClass({
  getInitialState: function () { return {N: 10, focused: null}; },
  changeN: function(e) { this.setState({N: e.target.value}); },
  focus: function (id, e) {
    this.setState({focused: toggle(this.state.focused, id)});
  },
  render: function () {
    var that = this;
    return D.div(null, [
      D.div(null, [
        D.span(null, 'N: '),
        D.input({value: this.state.N, onChange: this.changeN})
      ]),
      D.div(null,
        _.map(
          generateItems(this.state.N),
          function (i) { return React.createElement(Item, {
            key: i.id, item: i, appState: that.state, focF: that.focus.bind(null, i.id)
          });}
        )
      )
    ]);
  }
});

React.render(React.createElement(App), document.body);

有没有办法在不放弃漂亮的声明形式的情况下让这些小更新更有效率,或者 React 不适合这种规模?

【问题讨论】:

标签: javascript reactjs


【解决方案1】:

有几个潜在的解决方案可以让您继续使用漂亮的声明性模型:

1。使用shouldComponentUpdate

当您有很多元素时,shouldComponentUpdate 可以轻松取胜。您评论中的示例不太正确;相反,您想查看您关心的任何this.props 值是否与您关心的任何next_props 不同(this.statenext_state 相同。例如,仅重新渲染当焦点道具发生变化时,您可能会这样做:

shouldComponentUpdate: function (next_props, next_state) {
  return this.props.appState.focused !== next_props.appState.focused;
},

(尽管这个简单的示例不处理 ID 或处理程序的更改)。

虽然这是非常手动的,但您可以轻松构建抽象或混合来比较对象(浅或深,取决于您的道具的结构)以查看它们是否发生了变化:

var ShallowPropStateCompareMixin = {
  shouldComponentUpdate: function(nextProps, nextState) {
    return !shallowEquals(nextProps, this.props) ||
           !shallowEquals(nextState, this.state);
  }
}

var MyComponent = React.createClass({
  mixins: [ShallowPropStateCompareMixin],

  // ...
});

事实上,这已经实现为the PureRenderMixin。您可以在 this example 中看到这一点(请注意,其他因素可能会导致大量 DOM 元素的迟缓,包括影响盒子模型的扩展和内联样式)。

2。本地化状态变化

您可以使用的另一种技术是本地化状态更改;在您的示例中,每当每个项目悬停或未悬停时,顶级应用程序状态都会发生变化;相反,您可以将此行为委托给项目本身(或该项目的其他容器)。这样一来,只有单个容器项会发生状态更改,而大部分项根本不会重新渲染。

另一个我没有尝试过的想法是将 n 个项目中的 x 分组到一个分组组件中(例如,n=5000 和 x=100);然后,只有包含已更改项目的分组组件需要更新。您可以在分组组件上使用 shouldComponentUpdate,这样其他组件就不需要迭代了。

【讨论】:

  • 1) 所以在您的情况下,shouldComponentUpdate 方法基本上指定了与项目相关的全局应用程序状态的子集,对吧?这是一个合理的策略,但在这种情况下似乎并没有改善,因为除了focused ID 之外没有其他状态。至于 mixin,我原以为这是标准行为,如果 props 和 state 都没有改变,你为什么要重新渲染?
  • 2) 将状态移动到项目很有趣,如果应用程序的其他部分需要订阅此状态可能会出现问题,但它确实修复了缓慢并且即使使用 N= 也可以顺利运行50K jsfiddle.net/estolua/o3omfmae
  • @estolua 回复:mxin,JavaScript 很难确定是否发生了变化,因为更改深度嵌套的对象或数组中的键不会使 === 为对象返回 false或数组。 React 团队选择不将其设为默认值,以防止出现细微的错误。
  • 这两个本地化的状态变化会执行得更好。您也可以在使用事件发射器在其他地方管理数据的情况下执行此操作,其中每个组件都根据其道具订阅事件名称。
  • @estolua 要向上传播这些内容,您只需创建一个自定义“事件”属性:jsfiddle.net/o3omfmae/3 很像默认的 html 元素接受(onChange、onSubmit 等)。 shouldComponentUpdate 实际上与 OP 的问题完全无关,应该从答案中删除,这完全是为了处理它所属的状态。
【解决方案2】:

如果将 shouldComponentUpdate 添加到 Item 组件,它会变得更快一些。因为它只呈现正在更改的项目。但是我的机器上有 5000 个项目似乎仍然有点慢。

var Item = React.createClass({

    shouldComponentUpdate: function(nextProps, nextState){
        return (nextProps.appState.focused==nextProps.item.id) || (this.props.appState.focused==this.props.item.id);
    },

【讨论】:

    【解决方案3】:

    在我的系统上 5000 仍然可以,但在现实世界场景中你真的需要渲染 > 1000 个 dom 节点吗?如果您非常关心性能,也许您可​​以考虑Mithril。它也使用虚拟 dom 方法,但非常小而且速度很快。

    【讨论】:

    • 是的,虚拟 dom 是大型 DOM 的最佳选择!
    • 它很容易变成 > 1000 个节点,而且它们会比这个玩具示例中的轻量级 div 重得多。桌面系统的性能可能会变得可以接受,但也有移动设备。将这种场景与其他 vDOM 实现进行基准测试可能会很有趣,一定会在某个时候检查 Mithril 和 Mercury。
    • @estolua 通常在这里获得性能的最佳方法是使用无限表方法,即仅渲染可见的内容。在移动设备上,您可能只能看到这 5000 个 div 中的 10 个左右。
    猜你喜欢
    • 2015-04-13
    • 2014-11-14
    • 2016-08-26
    • 2016-01-16
    • 2012-09-23
    • 1970-01-01
    • 2011-07-15
    • 2019-03-22
    • 2011-08-06
    相关资源
    最近更新 更多