我以TodoList示例来反射(reflect)我的问题,但显然我的实际代码更加复杂。
我有一些这样的伪代码。
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并且一切正常。修改待办事项数据后,仅父项和已编辑的待办事项会重新呈现。
问题在于,随着用户的滚动,有时我的列表会越来越大。并且,当更新单个待办事项时,渲染父项,在所有待办事项上调用
shouldComponentUpdate
并渲染单个待办事项需要花费越来越多的时间。如您所见,Todo组件具有与Todo数据不同的其他组件。这是所有待办事项渲染所需的数据并被共享(例如,我们可以想象待办事项有一个“displayMode”)。具有许多属性会使
shouldComponentUpdate
的执行速度变慢。另外,使用
ReactCSSTransitionGroup
似乎也有点慢,因为ReactCSSTransitionGroup
必须甚至在调用todos的ReactCSSTransitionGroupChild
之前就渲染自己和shouldComponentUpdate
。 React.addons.Perf
显示ReactCSSTransitionGroup > ReactCSSTransitionGroupChild
渲染浪费了列表的每个项目。因此,据我所知,我使用
PureRenderMixin
,但列表较大时,这可能还不够。我仍然表现不差,但是想知道是否有简单的方法可以优化我的渲染。任何的想法?
编辑:
到目前为止,我的大列表是分页的,因此,我没有大的项目列表,而是现在将此大列表拆分为页面列表。由于每个页面现在都可以实现
shouldComponentUpdate
,因此可以实现更好的性能。现在,当项目在页面中发生更改时,React只需调用在页面上迭代的主要render函数,而仅从单个页面调用render函数,这将减少迭代工作。但是,我的渲染性能仍然与我拥有的页码(O(n))成线性关系。因此,如果我有成千上万的页面,则仍然是一个问题:)在我的用例中,这不太可能发生,但我仍然对更好的解决方案感兴趣。
我很确定可以通过将大列表拆分成树(如持久数据结构)以及每个节点的位置来实现O(log(n))渲染性能,其中n是项目(或页面)的数量。有权使用
shouldComponentUpdate
短路计算是的,我正在考虑类似于Scala或Clojure中的Vector之类的持久数据结构:
但是我担心React,因为据我所知,渲染树的内部节点时可能必须创建中间dom节点。根据用例(and may be solved in future versions of React),这可能是一个问题
同样,当我们使用Javascript时,我想知道Immutable-JS是否支持它,并使“内部节点”可访问。另请:https://github.com/facebook/immutable-js/issues/541
编辑:与实验相关的有用链接:Can a React-Redux app really scale as well as, say Backbone? Even with reselect. On mobile
最佳答案
在我们的产品中,我们还遇到了与呈现的代码量有关的问题,并且我们开始使用可观察对象(请参见此blog)。这可能会部分解决您的问题,因为更改待办事项将不再需要重新渲染保存该列表的父组件(但添加仍然可以)。
它也可以帮助您更快地重新渲染列表,因为当props在shouldComponentUpdate上更改时,您的todoItem组件可能只是返回false。
为了在呈现概述时进一步提高性能,我认为您的树/分页想法确实不错。使用可观察的数组,每个页面可以开始侦听一定范围内的数组拼接(使用ES7 polyfill或可移动的)。这会带来一些管理,因为这些范围可能会随着时间而变化,但是应该使您进入O(log(n))
这样您会得到类似:
var TodosContainer = React.createClass({
componentDidMount() {
this.props.todos.observe(function(change) {
if (change.type === 'splice' && change.index >= this.props.startRange && change.index < this.props.endRange)
this.forceUpdate();
});
},
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.slice(this.props.startRange, this.props.endRange).map(this.renderTodo)
return (
<ReactCSSTransitionGroup transitionName="transition-todo">
{todos}
</ReactCSSTransitionGroup>,
);
}
});
大列表和 react 的中心问题似乎是您不能仅将新的DOM节点移入dom。否则,您根本不需要“页面”就可以将数据分成较小的块,并且您可以像在此jsFiddle中使用JQuery一样将一个新的Todo项拼接到dom中。如果您为每个待办事项使用
ref
,您仍然可以通过react来做到这一点,但是我认为这可能会绕过系统,因为这可能会破坏对帐系统?