我拿了一个TodoList示例来反映我的问题,但显然我的真实代码更复杂。
我有一些像这样的伪代码。
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数据时,仅重新渲染父级和编辑的待办事项。
问题是,在某些时候,当用户滚动时,我的列表会变大。当一个Todo更新时,渲染父节点需要花费越来越多的时间,在所有todos上调用shouldComponentUpdate
,然后渲染单个待办事项。
如您所见,Todo组件具有除Todo数据之外的其他组件。这是所有待办事项渲染所需的数据并且是共享的(例如,我们可以想象有&#34; displayMode&#34;对于todos)。拥有许多属性会使shouldComponentUpdate
执行速度稍慢。
此外,使用ReactCSSTransitionGroup
似乎也会慢下来,因为ReactCSSTransitionGroup
甚至必须在调用ReactCSSTransitionGroupChild
todos之前呈现自己和shouldComponentUpdate
。 React.addons.Perf
表示ReactCSSTransitionGroup > ReactCSSTransitionGroupChild
呈现是列表中每个项目浪费的时间。
所以,据我所知,我使用的是PureRenderMixin
,但是如果列表较大,这可能还不够。我仍然没有那么糟糕的表现,但想知道是否有简单的方法来优化我的渲染。
有什么想法吗?
修改:
到目前为止,我的大列表是分页的,所以我现在将这个大列表分成一个页面列表,而不是列出大项目。这样可以获得更好的性能,因为每个页面现在都可以实现shouldComponentUpdate
。现在,当一个项目在页面中发生变化时,React只需要调用在页面上迭代的主渲染函数,并且只从单个页面调用渲染函数,这样可以减少迭代次数。
但是,我的渲染性能仍然与我的页码(O(n))呈线性关系。因此,如果我有数千个页面,它仍然是同一个问题:)在我的用例中,它不太可能发生,但我仍然对更好的解决方案感兴趣。
我很确定通过将大型列表拆分为树(如持久性数据结构),可以实现O(log(n))渲染性能,其中n是项目(或页面)的数量,并且其中每个节点都有权使用shouldComponentUpdate
是的我正在考虑类似于Scala或Clojure中的Vector等持久数据结构的东西:
然而,我关注React,因为据我所知,在渲染树的内部节点时可能需要创建中间dom节点。根据用例(and may be solved in future versions of React)
,这可能是一个问题同样,当我们使用Javascript时,我想知道Immutable-JS是否支持这一点,并且制作&#34;内部节点&#34;无障碍。请参阅: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
答案 0 :(得分:9)
在我们的产品中,我们还遇到了与呈现代码量相关的问题,我们开始使用observables(请参阅此blog)。这可能会部分解决您的问题,因为更改待办事项将不再需要重新呈现包含列表的父组件(但仍然添加)。
它还可以帮助您更快地重新呈现列表,因为当在propsComponentUpdate上更改props时,todoItem组件可能只返回false。
为了在渲染概述时进一步提高性能,我认为您的树/分页想法确实很好。使用可观察数组,每个页面都可以开始在一定范围内侦听数组拼接(使用ES7 polyfill或mobservable)。这将引入一些管理,因为这些范围可能会随着时间的推移而改变,但应该让你到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>,
);
}
});
大型列表和反应的核心问题似乎是您无法将新的DOM节点转移到dom中。但是,你不会需要这些页面&#39;完全按照较小的块来划分数据,你可以将一个新的Todo项目拼接到dom中,就像在jsFiddle中使用JQuery一样。如果你对每个待办事项使用ref
,你仍然可以做出反应,但是这会解决系统问题,因为它可能会破坏对帐系统?
答案 1 :(得分:5)
这是我用ImmutableJS内部结构完成的POC实现。这不是一个公共API,所以它还没有为生产做好准备,目前还没有处理极端情况但它可以工作。
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
< / p>
答案 2 :(得分:0)
最近我有一个性能瓶颈试图渲染一个包含500多条记录的表,减速器是不可变的,我使用重新选择来记忆复杂的选择器,在拉了一些头发之后,我发现问题解决了所有选择器的记忆。