首页 > 代码库 > React 组件性能优化探索实践
React 组件性能优化探索实践
转自:http://www.tuicool.com/articles/Ar6Zruq
React本身就非常关注性能,其提供的虚拟DOM搭配上Diff算法,实现对DOM操作最小粒度的改变也是非常的高效。然而其组件渲染机制,也决定了在对组件进行更新时还可以进行更细致的优化。
react组件渲染
react的组件渲染分为初始化渲染和更新渲染。
在初始化渲染的时候会调用根组件下的所有组件的render方法进行渲染,如下图(绿色表示已渲染,这一层是没有问题的):
但是当我们要更新某个子组件的时候,如下图的绿色组件(从根组件传递下来应用在绿色组件上的数据发生改变):
我们的理想状态是只调用关键路径上组件的render,如下图:
但是react的默认做法是调用所有组件的render,再对生成的虚拟DOM进行对比,如不变则不进行更新。这样的render和虚拟DOM的对比明显是在浪费,如下图(黄色表示浪费的render和虚拟DOM对比)
那么如何避免发生这个浪费问题呢,这就要牵出我们的 shouldComponentUpdate
shouldComponentUpdate
react在每个组件生命周期更新的时候都会调用一个 shouldComponentUpdate(nextProps, nextState)
函数。它的职责就是返回true或false,true表示需要更新,false表示不需要,默认返回为true,即便你没有显示地定义 shouldComponentUpdate 函数。这就不难解释上面发生的资源浪费了。
为了进一步说明问题,我们再引用一张官网的图来解释,如下图( SCU表示shouldComponentUpdate,绿色表示返回true(需要更新),红色表示返回false(不需要更新);vDOMEq表示虚拟DOM比对,绿色表示一致(不需要更新),红色表示发生改变(需要更新)):
根据渲染流程,首先会判断shouldComponentUpdate(SCU)是否需要更新。如果需要更新,则调用组件的render生成新的虚拟DOM,然后再与旧的虚拟DOM对比(vDOMEq),如果对比一致就不更新,如果对比不同,则根据最小粒度改变去更新DOM;如果SCU不需要更新,则直接保持不变,同时其子元素也保持不变。
C1根节点,绿色SCU (true),表示需要更新,然后vDOMEq红色,表示虚拟DOM不一致,需要更新。
C2节点,红色SCU (false),表示不需要更新,所以C4,C5均不再进行检查
C3节点同C1,需要更新
C6节点,绿色SCU (true),表示需要更新,然后vDOMEq红色,表示虚拟DOM不一致,更新DOM。
C7节点同C2
C8节点,绿色SCU (true),表示需要更新,然后vDOMEq绿色,表示虚拟DOM一致,不更新DOM。
为了避免一定程度的浪费,react官方还在0.14版本中加入了无状态组件,如下:
// es5 function HelloMessage(props) { return <div>Hello {props.name}</div>; }
// es6 const HelloMessage = (props) => <div>Hello {props.name}</div>;
具体可参考官网: Reusable Components
既然明白了这关键所在,现在是时候向我们的大大小小一箩筐组件开刀了。
牛刀小试,直接把一些不需要更新的组件返回false
下面我们以音量图标为例,这是一个svg图标,不需要更新,所以直接return false
import React, {Component} from ‘react‘; class Mic extends Component { constructor(props) { super(props); } shouldComponentUpdate() { return false; } render() { return ( <svg className="icon-svg icon-mic" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" aria-labelledby="title"> <title>mic</title> <path className="path1" d="M15 22c2.761 0 5-2.239 5-5v-12c0-2.761-2.239-5-5-5s-5 2.239-5 5v12c0 2.761 2.239 5 5 5zM22 14v3c0 3.866-3.134 7-7 7s-7-3.134-7-7v-3h-2v3c0 4.632 3.5 8.447 8 8.944v4.056h-4v2h10v-2h-4v-4.056c4.5-0.497 8-4.312 8-8.944v-3h-2z"></path> </svg> ) } } export default Mic;
登堂入室,对数据进行对比确定是否需要更新
先来个官网的例子,通过判断id是否改变来确定是否需要更新:
shouldComponentUpdate: function(nextProps, nextState) { return nextProps.id !== this.props.id; }
看起来也没那么玄乎,直接一个 !==
对比下就ok了,那是不是所有的都可以这样直接对比就可以呢? 我们先来看下js的两个数据类型(原始类型与引用类型)的各自比较
// 原始类型var a = ‘hello the‘;var b = a; b = b + ‘world‘;console.log(a === b); // false// 引用类型var c = [‘hello‘, ‘the‘];var d = c; d.push(‘world‘);console.log(c === d); // true
我们可以看到a和b不等,但是c和d是一样一样的,这修改了d,也直接修改了c,那还怎么对比(关于原始类型与引用类型的区别这里就不说明了)。
现在看来我们得分情况处理了,原始类型数据和引用类型数据得采用不同的办法处理。
原始类型数据
这没什么好说的,直接比对就是了。但是每个人都是想偷懒的,这要是每个组件都要这样去写下也挺麻烦的,于是react官方有了插件帮我们搞定这事:
PureRenderMixin (es5的插件)
var PureRenderMixin = require(‘react-addons-pure-render-mixin‘); React.createClass({ mixins: [PureRenderMixin], render: function() { return <div className={this.props.className}>foo</div>; } });
Shallow Compare (es6的插件)
var shallowCompare = require(‘react-addons-shallow-compare‘); export class SampleComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this, nextProps, nextState); } render() { return <div className={this.props.className}>foo</div>; } }
引用类型数据
既然引用类型数据一直返回true,那就得想办法处理,能不能把前后的数据变成不一样的引用呢,那样不就不相等了吗?于是就有了我们的不可变数据。
react官方提供了一个 Immutability Helpers
const newValue = http://www.mamicode.com/{"hljs-regexp">// 在这里做你想要的修改};// 快速检查 —— 只要检查引用newValue =http://www.mamicode.com/== oldValue; // false// 如果你愿意也可以用 Object.assign 语法const newValue2 = Object.assign({}, oldValue);
newValue2 === oldValue; // false
然后在 shouldComponentUpdate
中进行比对
shouldComponentUpdate(nextProps) {
return isObjectEqual(this.props, nextProps);
}
我们目前采用的是在reducer里面更新数据使用 Object.assign({}, state, {newkey: newValue}
(数据管理采用redux),然后在组件里面根据某个具体的字段判断是否更新,如title或id等,而不是判断整个对象:
shouldComponentUpdate: function(nextProps, nextState){
return nextProps.title !== this.props.title;
}
React 组件性能优化探索实践