ReactJS缓慢,频繁更新到大DOM

ReactJS sluggish with frequent updates to big DOM?

本文关键字:DOM 更新 缓慢 ReactJS      更新时间:2023-09-26

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

我担心的一件事是,虚拟 DOM 在每次微小的状态更改时都会被完整地重新计算。我可以看到这个模型的好处,但是在一个有很多元素和频繁的小更新的应用程序中,这会导致像"悬停"效果这样简单的事情产生大量的开销。

例如,这会呈现一系列Ndiv 并更改 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 只是不适合这种规模?

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

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],
  // ...
});

事实上,这已经作为PureRenderMixin实现。您可以在此示例中看到此操作(请注意,其他内容可能会导致许多 DOM 元素(包括影响框模型的扩展和内联样式)的迟缓)。

2. 本地化状态更改

可以使用的另一种技术是本地化状态更改;在您的示例中,每当悬停或取消悬停每个项时,顶级应用程序状态都会更改;相反,您可以将此行为委托给项本身(或该项的其他容器)。这样,只有单个容器项才会有状态更改,并且大多数项根本不会重新呈现。

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

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

如果你将 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);
    },