React 以其性能著称因为它有虚拟 DOM, 并且只在需要的时候才去更新真正的 DOM, 所以它始终可以比直接更新 DOM 快得多然而, React 也就这么智能了(此刻!), 所以我们要了解它的模式和限制, 这样才不会在某些原因下破坏了它的性能
我们需要注意的一个方面是 React 如何决定何时重新渲染组件不是在更新 DOM 渲染中, 而只是调用 render 方法来更改虚拟 DOM 我们应该告诉它什么时候应该渲染我们再来看看这两个...
1. 组件的状态改变
重新渲染只能在组件状态发生变化时触发组件状态可以从改变 props 改变, 或者从一个直接的 setState 变化组件获取更新状态, React 决定是否应该重新渲染组件不幸的是, 默认情况下, React 是非常简单的, 基本上重新渲染所有的东西
组件已更改? 重新渲染父元素改变了? 重新渲染实际上不影响视图的部分改变了? 重新渲染
- 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 甚至没有改变它的价值!
但是一直重新渲染是没有任何帮助的...
我的意思是, 我很欣赏 React 非常仔细如果状态发生变化, 而组件进行重新渲染, 情况会更糟糕我怎么知道我的朋友寄给我的新消息? 我会错过的, 而且她可能会认为这是故意的, 那么她就不再和我说话, 整个友谊就会毁了这样做的风险很高重新渲染绝对是安全的选择
但重新渲染似乎影响性能
是的, 重新渲染不必要的生命周期, 通常不是一个好主意然而, React 不知道何时可以忽略部分状态因此, 只要状态发生变化, 重要或不重要, 它就全部重新呈现
我们如何告诉 React 跳过重新渲染?
那好吧, 我们来讲第二点...
2. shouldComponentUpdate 方法
默认情况下, shouldComponentUpdate 返回 true 这就是我们上面看到的一直更新所有东西的原因但是如果你需要提升性能, shouldComponentUpdate 可以更智能与其让 react 重新渲染所有组件, 你可以告诉 react, 你不希望触发 re-render
当 React 渲染组件时, 它会执行 shouldComponentUpdate, 看看它是否返回 true(组件应该更新, 也就是重新渲染)或 false(React 可以跳过这次重新渲染)所以你需要重写 shouldComponentUpdate 返回 true 还是 false 来告诉 React 什么时候重新渲染以及什么时候跳过
当你使用 shouldComponentUpdate 的时候, 你需要决定哪些数据实际上对于重新渲染很重要让我们回到我们的例子:
- class Todo extends React.Component {
- componentDidMount() {
- setInterval(() => {
- this.setState(() => {
- console.log('setting state');
- return { unseen: "does not display" }
- });
- }, 1000);
- }
- shouldComponentUpdate(nextProps) {
- const differentTitle = this.props.title !== nextProps.title;
- const differentDone = this.props.done !== nextProps.done
- return differentTitle || differentDone;
- }
- render() {
- console.log('render called');
- return (<div>...</div>);
- }
- }
正如你所看到的, 我们只需要在 title 或 done 属性发生了变化才重新渲染 Todo 如果我们不在乎 unseen 是否改变了, 所以我们不把它写在 shouldComponentUpdate 里
当 React 渲染一个 Todo 组件时 (如由 setState 触发), 它将首先检查状态是否已经改变(通过 props 或 state) 假设状态不同(这是因为我们做了一个明确的 setState 调用),React 将检查 shouldComponentUpdateTodo 组件 React 会评估 shouldComponentUpdate 是否是真的, 并决定在那里渲染
更新后的代码中 setState 仍然会每一秒都执行一次, 但 render 只能在初始加载(或当 title 或 done 属性发生改变时)
这个例子特别详细, 因为有两个我们关心的属性 (title 和 done), 只有一个属性我们需要忽略(unseen) 根据你的数据, 只检查一个或两个属性可能会更有意义
返回 false 不会阻止子组件在其状态更改时重新渲染
这适用于子组件的 state 但不是他们的 props 所以如果一个子组件在内部管理其状态的某个方面(用 setState 它自己的状态), 那么它仍然会被更新但是, 如果父组件返回 false,shouldComponentUpdate 则不会将更新传递 props 给子组件, 因此, 即使子组件 props 已经更新, 子组件也不会重新渲染
简单的性能测试
编写和运行计算 shouldComponentUpdate 可能是昂贵的, 所以你应该确保他们值得的时间在编写任何 shouldComponentUpdates 之前, 您可以检查 React 在默认情况下的浪费周期数通过这些信息来指导你, 你可以知道哪些组件渲染太频繁而导致性能瓶颈
使用 React 的性能工具来查找浪费的周期:
- Perf.start()
- // Do the render
- Perf.stop()
- Perf.printWasted()
哪些组件浪费了很多渲染周期? 你怎么能让他们更聪明的 shouldComponentUpdate? 尝试一些方法, 并确保使用性能工具相互检查它们!
PS
最后讲一讲 functional stateless components 和 stateless class-based components 在 ShouldComponentUpdate 的差异
- const FuncComponent = ({ some, props, here}) => <fancymarkup/>
- class ClassicalComponent extends Component {
- render() {
- return < fancymarkup / >
- }
- }
这两个组件表现上一致实际上, ClassicalComponent 实现了 ShouldComponentUpdate 方法并且可以排除任何不必要的 re-render 所以, 如果你有一个使用了大量 stateless functional components 的大型 APP, 将这些 functional components 重写成 class-based components, 你会明显的看到性能提升
来源: http://www.jianshu.com/p/2223db6db4bd