反应性能:使用PureRenderMixin呈现大型列表 [英] React performance: rendering big list with PureRenderMixin

查看:112
本文介绍了反应性能:使用PureRenderMixin呈现大型列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我拿了一个TodoList示例来反映我的问题,但显然我的真实代码更复杂。

I took a TodoList example to reflect my problem but obviously my real-world code is more complex.

我有一些这样的伪代码。

I have some pseudo-code like this.

var Todo = React.createClass({
  mixins: [PureRenderMixin], 
  ............ 
}

var TodosContainer = React.createClass({
  mixins: [PureRenderMixin],    

  renderTodo: function(todo) {
     return <Todo key={todo.id} todoData={todo} x={this.props.x} y={this.props.y} .../>;
  },

  render: function() {
     var todos = this.props.todos.map(this.renderTodo)
     return (
          <ReactCSSTransitionGroup transitionName="transition-todo">
                 {todos}
          </ReactCSSTransitionGroup>,
     );
  }

});

我的所有数据都是不可变的,并且PureRenderMixin使用得当且一切正常。当Todo数据被修改时,只有亲并且重新渲染已编辑的待办事项。

All my data is immutable, and PureRenderMixin is used appropriately and everything works fine. When a Todo data is modified, only the parent and the edited todo is re-rendered.

问题是,在某些时候,当用户滚动时,我的列表会变大。当更新单个Todo时,渲染父节点需要花费越来越多的时间,在所有待办事项上调用 shouldComponentUpdate ,然后渲染单个待办事项。

The problem is that at some point my list grows big as the user is scrolling. And when a single Todo is updated, it takes more and more time to render the parent, call shouldComponentUpdate on all the todos, and then render the single todo.

如您所见,Todo组件具有除Todo数据之外的其他组件。这是所有待办事项渲染所需的数据并且是共享的(例如,我们可以想象todos有一个displayMode)。拥有许多属性会使 shouldComponentUpdate 执行得慢一点。

As you can see, the Todo component has other component than the Todo data. This is data that is required for render by all the todos and is shared (for example we could imagine there's a "displayMode" for the todos). Having many properties makes the shouldComponentUpdate perform a little bit slower.

此外,使用 ReactCSSTransitionGroup 似乎也放慢了一点,因为 ReactCSSTransitionGroup 甚至在调用todos的 shouldComponentUpdate 之前,必须呈现自身和 ReactCSSTransitionGroupChild React.addons.Perf 显示 ReactCSSTransitionGroup> ReactCSSTransitionGroupChild 渲染是列表中每个项目浪费的时间。

Also, using ReactCSSTransitionGroup seems to slow down a little too, as ReactCSSTransitionGroup have to render itself and ReactCSSTransitionGroupChild even before the shouldComponentUpdate of todos is called. React.addons.Perf shows that ReactCSSTransitionGroup > ReactCSSTransitionGroupChild rendering is time wasted for each item of the list.

所以,据我所知,我使用 PureRenderMixin 但是有了更大的列表,这可能还不够。我仍然没有那么糟糕的表现,但想知道是否有简单的方法来优化我的渲染。

So, as far as I know, I use PureRenderMixin but with a larger list this may be not enough. I still get not so bad performances, but would like to know if there are easy ways to optimize my renderings.

任何想法?

编辑

到目前为止,我的大名单是分页的,因此,我现在将这个大列表分成一个页面列表,而不是拥有一个大项目列表。这样可以获得更好的性能,因为每个页面现在都可以实现 shouldComponentUpdate 。现在,当一个项目在页面中发生变化时,React只需要调用在页面上迭代的主渲染函数,并且只从单个页面调用渲染函数,这样可以减少迭代次数。

So far, my big list is paginated, so instead of having a big list of items, I now split this big list in a list of pages. This permits to have better performances as each page can now implement shouldComponentUpdate. Now when an item changes in a page, React only has to call the main render function that iterates on the page, and only call the render function from a single page, which make a lot less iteration work.

但是,我的渲染性能仍然与我的页码(O(n))呈线性关系。所以如果我有成千上万的页面仍然是同样的问题:)在我的用例中它不太可能发生,但我仍然对更好的解决方案感兴趣。

However, my render performance is still linear to the page number (O(n)) I have. So if I have thousands of pages it's still the same issue :) In my usecase it's unlikely to happen but I'm still interested in a better solution.

我很漂亮确保有可能实现O(log(n))渲染性能,其中n是项目(或页面)的数量,通过将大型列表拆分为树(如持久性数据结构),以及每个节点具有的功能使用来短路计算:shouldComponentUpdate

I am pretty sure it is possible to achieve O(log(n)) rendering performance where n is the number of items (or pages), by splitting a large list into a tree (like a persistent data structure), and where each node has the power to short-circuit the computation with shouldComponentUpdate

是的我正在考虑类似持久数据结构的东西,比如Vector在Scala或Clojure中:

Yes I'm thinking of something akin to persistent data structures like Vector in Scala or Clojure:

但是我很关心React,因为据我所知它可能需要创建中间dom节点渲染树的内部节点。这可能是一个问题根据用例(,可能会在未来版本的React中解决

However I'm concerned about React because as far as I know it may have to create intermediate dom nodes when rendering the internal nodes of the tree. This may be a problem according to the usecase (and may be solved in future versions of React)

另外,因为我们使用Javascript,我想知道Immutable-JS是否支持这一点,并使内部节点可访问。请参阅: https://github.com/facebook/immutable-js/issues/541

Also as we are using Javascript I wonder if Immutable-JS support this, and make the "internal nodes" accessible. See: https://github.com/facebook/immutable-js/issues/541

编辑:与我的实验有用的链接: React-Redux应用程序真的可以扩展,比如Backbone吗?即使重新选择。在移动设备上

Edit: useful link with my experiments: Can a React-Redux app really scale as well as, say Backbone? Even with reselect. On mobile

推荐答案

这是我用ImmutableJS内部结构完成的POC实现。这不是一个公共API,所以它还没有准备好生产,目前不处理极端情况,但它可以工作。

Here is a POC implementation I've done with ImmutableJS internal structure. This is not a public API so it is not ready for production and does not currently handle corner cases but it works.

var ImmutableListRenderer = React.createClass({
  render: function() {
    // Should not require to use wrapper <span> here but impossible for now
    return (<span>
        {this.props.list._root ? <GnRenderer gn={this.props.list._root}/> : undefined}
        {this.props.list._tail ? <GnRenderer gn={this.props.list._tail}/> : undefined}
</span>);
  }   
})

// "Gn" is the equivalent of the "internal node" of the persistent data structure schema of the question
var GnRenderer = React.createClass({
    shouldComponentUpdate: function(nextProps) {
      console.debug("should update?",(nextProps.gn !== this.props.gn));
      return (nextProps.gn !== this.props.gn);
    },
    propTypes: {
        gn: React.PropTypes.object.isRequired,
    },
    render: function() {
        // Should not require to use wrapper <span> here but impossible for now
        return (
            <span>
                {this.props.gn.array.map(function(gnItem,index) { 
                    // TODO should check for Gn instead, because list items can be objects too...
                    var isGn = typeof gnItem === "object"
                    if ( isGn ) {
                        return <GnRenderer gn={gnItem}/>
                    } else {
                        // TODO should be able to customize the item rendering from outside
                        return <span>{" -> " + gnItem}</span>
                    }
                }.bind(this))}
            </span>
        );
    }
})

客户端代码看起来像

React.render(
    <ImmutableListRenderer list={ImmutableList}/>, 
    document.getElementById('container')
);

这是 JsFiddle 在列表的单个元素(大小N)更新后记录 shouldComponentUpdate 调用的次数:这不需要调用N次 shouldComponentUpdate

Here is a JsFiddle that logs the number of shouldComponentUpdate calls after a single element of the list (size N) is updated: this does not require to call N times shouldComponentUpdate

ImmutableJs github问题

这篇关于反应性能:使用PureRenderMixin呈现大型列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆