在React上处理渲染性能时,想知道解决此性能问题的最佳方法是什么。(为了清晰起见,代码过于简化)
var TodoList = React.createClass({
getInitialState: function () {
return { todos: Immutable.List(['Buy milk', 'Buy eggs']) };
},
onTodoChange: function (index, newValue) {
this.setState({
todos: this.state.todos.set(index, newValue)
});
},
render: function () {
return (
this.state.todos.map((todo, index) =>
<TodoItem
value={todo}
onChange={this.onTodoChange.bind(null, index)} />
)
);
}
});
假设只更改了一个待办事项。首先,将调用TodoList.render()
并再次开始重新渲染列表。由于TodoItem.onChange
通过bind
绑定到事件处理程序,因此,每次调用TodoList.render()
时,TodoItem.onChange
都会有一个新值。这意味着,即使我们将React.addons.PureRenderMixin
应用于TodoItem.mixins
,即使它们的value
没有改变,也不会重新渲染一个而是所有的TodoItem
。
我相信有多种方法可以解决这个问题,我正在寻找一种优雅的方法来解决这个问题。
在React中循环使用UI组件时,需要使用key
属性。这样可以进行同类比较。您可能会在控制台中看到以下警告。
警告:数组或迭代器中的每个子级都应该有一个唯一的"键"道具。
使用index
属性作为键是很诱人的,如果列表是静态的,这可能是一个不错的选择(如果只是为了消除警告的话)。但是,如果列表是动态的,则需要更好的密钥。在这种情况下,我会选择todo项本身的值。
render: function () {
return (
this.state.todos.map((todo, index) => (
<TodoItem
key={todo}
value={todo}
onChange={this.onTodoChange.bind(null, index)}
/>
))
);
}
最后,我认为你关于onChange
性质的猜想是错误的。是的,每次渲染时都是不同的特性。但该属性本身没有渲染效果,因此在虚拟DOM比较中没有发挥作用。
更新
(此答案已根据以下对话更新。)
虽然对onChange
这样的非基于渲染的道具的更改确实不会触发重新渲染,但它会触发虚拟DOM比较。根据应用程序的大小,这可能很昂贵,也可能很琐碎。
如果有必要避免这种比较,则需要实现组件的shouldComponentUpdate
方法,以忽略对非基于渲染的道具的任何更改。例如
shouldComponentUpdate(nextProps) {
const ignoreProps = [ 'onChange' ];
const keys = Object.keys(this.props)
.filter((k) => ignoreProps.indexOf(k) === -1);
const keysNext = Object.keys(nextProps)
.filter((k) => ignoreProps.indexOf(k) === -1);
return keysNext.length !== keys.length ||
keysNext.some((k) => nextProps[k] !== this.props[k]);
}
如果使用state,还需要比较nextState
和this.state
。