React因为他的性能而著名。因为他有一个虚拟DOM层并且只有在需要时才更新真实DOM。即使是同样地信息这也比一直直接更新DOM要快很多。但是,React的智能仅此而已(目前为止),我们的任务是知道React的预期行为以及限制,这样我们才不会意外损失性能。
我们需要关注的一方面是React如何决定什么时候重新渲染组件。不是重新渲染DOM节点,只是调用render
方法来改变虚拟DOM。我们可以通过告诉React什么时候需要渲染什么时候不需要渲染来帮助React。让我们依次来看看这些。
1. 组件的状态发生改变
只有在组件的state变化时才会出发组件的重新渲染。状态的改变可以因为props
的改变,或者直接通过setState
方法改变。组件获得新的状态然后React决定是否应该重新渲染组件。不幸的是,React难以置信简单地将默认行为设计为每次都重新渲染。
组件改变?重新渲染。父组件改变?重新渲染。一部分没有导致视图改变的props改变?重新渲染。
class Todo extends React.Component {
componentDidMount() {
setInterval(() => {
this.setState(() => {
console.log('setting state');
return { unseen: "does not display" }
});
}, 1000);
}
render() {
console.log('render called');
return (<div>...</div>);
}
}
在这个(非常刻意的)例子中,Todo
将会每秒重新渲染依次,即使render
方法根本没有使用unseen
。事实上,unseen
值甚至都不改变。你可以在CodePen里查看这个例子的实际版本。
好吧,但是每次都重新渲染没有什么帮助。
我的意思是,我非常感谢React的细心谨慎。如果状态改变但是组件没有正确渲染的话更糟。权衡之下,每次都重新渲染绝对是一个安全的选择。
但是重新渲染的时间成本看起来非常昂贵(例子里非常夸张地表现了出来)。
是的,在不必要的时候重新渲染会浪费循环并且不是一个好的想好。但是,React不能知道什么时候可以安全的跳过重新渲染,所以React无论是否重要每次都重新渲染。
我们如何告诉React跳过重新渲染?
那就是第二点要说的内容。
2. shouldComponentUpdate
方法
shouldComponentUpdate
方法默认返回true
,这就是导致每次更新都重新渲染的原因。但是你可以在需要优化性能时重写这个方法来让React更智能。比起让React每次都重新渲染,你可以告诉React你什么时候不像触发重新渲染。
当React将要渲染组件时他会执行shouldComponentUpdate
方法来看它是否返回true
(组件应该更新,也就是重新渲染)。所以你需要重写shouldComponentUpdate
方法让它根据情况返回true
或者false
来告诉React什么时候重新渲染什么时候跳过重新渲染。
当你使用shouldComponentUpdate
方法你需要考虑哪些数据对与重新渲染重要。让我们回到这个例子。
正如你所看到的,我们只想在title
和done
属性改变的时候重新渲染Todo
。我们不关心unseen
是否改变,所以我没有把它包含在shouldComponentUpdate
方法中。
当React渲染Todo
组件(通过setState
触发)他会首先检查状态是否改变(通过props
和state
)。假设状态改变了(因为我们显式地调用了setState
所以这会发生)React会检查Todo
的shouldComponentUpdate
方法。React会根据shouldComponentUpdate
方法返回值为true
或者false
来决定从哪里渲染。
更新后的代码仍然会每秒调用一次setState
但是render
只有在第一次加载时(或者title
或done
属性改变后)才会调用。你可以在这里看到。
看起来有很多工作去做。
是的,这个例子非常冗长因为有两个属性(title
和done
)需要关注并且只有一个可以忽略(unseen
)。根据你的数据可能仅检查一个或两个属性并且忽略其他会更有意义。
重要提示
当子组件的的state变化时, 返回false并不能阻止它们重渲染。
– Facebook的React文档
这作用于子组件的状态而不是他们的props
。所以如果一个子组件内部管理了一些他自己的状态(使用他自己的setState
),这仍然会更新。但是如果父组件的shouldComponentUpdate
方法返回了false
就不会传递更新后的props
给他的子组件,所以子组件不会重渲染,即使他们的props
变化了。
额外内容:简单性能测试
编写并且在shouldComponentUpdate
方法中运行计算的时间成本可能会很昂贵,所以你需要确保值得做。在写shouldComponentUpdate
方法前你可以测试React一个周期默认会消耗多少时间。有了这个信息做参考,在做性能优化时你可以做一个不盲目的决定。
使用React的性能工具去发现浪费的周期:
Perf.start()
// Do the render
Perf.stop()
Perf.printWasted()
哪一个组件浪费了很多渲染周期?你怎么通过shouldComponentUpdate
方法让他们更智能?试着使用性能测试工具来比较他们的性能。