共计 5493 个字符,预计需要花费 14 分钟才能阅读完成。
1. 单个 react 组件性能优化
1.1 render
里面尽量减少新建变量和 bind
函数的使用,尽量减少传递参数的数量
在 render
中绑定函数,无非就是下面三种:
render() {
return (
<div className='app'>
<span onClick={this.handleClick}>1</span>
<span onClick={this.handleClick.bind(this)}>2</span>
<span onClick={()=>this.handleClick()}>3</span>
</div>
)
}
第一种是在构造函数中绑定 this
,第二种是在render()
函数里面绑定 this
,第三种就是使用箭头函数,上述方法都能实现this
的绑定。
但是哪一种方法的性能最好,是我们要考虑的问题。毫无疑问第一种的性能最好。
第一种方法,构造函数每渲染一次便会执行一遍;
第二种方法,在每次 render()
的时候都会重新执行一遍函数;
第三种方法,每一次 render()
的时候,都会生成一个新的箭头函数,即使两个箭头函数的内容是一样的。
react
判断是否需要进行 render
是浅层比较 ,简单来说就是通过===
来判断的,如果 state
或者 prop
的类型是字符串或者数字,只要值相同,那么浅层比较就会认为其相同;
但是如果前者的类型是复杂的对象的时候,我们知道对象是引用类型,浅层比较只会认为这两个 prop
是不是同一个引用,如果不是,哪怕这两个对象中的内容完全一样,也会被认为是两个不同的prop
。
举个例子:
当我们给组件 App
名为 style
的prop
赋值;
<App style={{color:"green"}}
使用这种方法,每一次渲染都会被认为是一个 style
这个 prop
发生了变化,因为每一次都会产生一个对象给style
。
如果想要让 react
渲染的时候认为前后对象类型 prop
相同,则必须要保证 prop
指向同一个 javascript
对象,改进如下:
const appStyle = {color: "red"}; // 这个初始化只执行一次,不要放在 render 中,可以放在构造函数中
<App style={appStyle} />
1.2 定制 shouldComponentUpdate
函数
生命周期函数 shouldComponentUpdate
是决定 react
组件什么时候能够重新渲染的函数,但是这个函数默认的实现方式就是简单的返回一个 true
。也就是说,默认每次更新的时候都要调用所用的生命周期函数,包括render
函数,重新渲染。
看看下面这个例子:
class App extends React.Component {constructor(props) {super(props);
this.state = {
count = 2,
name = 'apple',
}
this.handleClick = this.handleClick.bind(this);
this.handleName = this.handleName.bind(this);
}
this.handleClick() {// ...}
this.handleName() {// ...}
render() {
return (
<div className='app'>
<span> 数量,{this.state.count}</span>
<button onClick={this.handleClick}> 改变数量 </button>
<button onClick={this.handleName}> 改变名字 </button>
<Child title={this.state.name}></Child>
</div>
);
}
}
class Child extends React.Component {render() {console.log('render 了一次');
return (<h3> 我想吃,{this.props.title}</h3>
);
}
}
我们写了两个组件,App
和 Child
组件,并写两个方法,一个改变 App
中的 count
的值,一个是改变 name
,我们在Child
的render
中打印了每次是否执行。
不出意外,虽然 Child
组件里的 title
值没有改变,但是还是 render
了。
为了进一步优化这个问题,我们这样改 Child
组件:
class Child extends React.Component {shouldComponentUpdate(nextProps,nextState) {if(nextProps.title == this.props.title) {return false;}
return true;
}
render() {console.log('render 了一次');
return (<h3> 我想吃,{this.props.title}</h3>
);
}
}
只有当 Child
的title
值发生改变的时候,组件才会去render
。
在最新的 react
中,react
给我们提供了 React.PureComponent
,官方也在早期提供了名为react-addons-pure-render-mixin
插件来重新实现 shouldComponentUpdate
生命周期方法。
class Child extends React.PureComponent {// shouldComponentUpdate(nextProps,nextState) {// if(nextProps.title == this.props.title) {
// return false;
// }
// return true;
// }
render() {console.log('render 了一次');
return (<h3> 我想吃,{this.props.title}</h3>
);
}
}
通过上述的方法的效果也是和我们先前定制 shouldComponentUpdate
的效果是一致的。
但是我们要注意的是,这里的 PureRender
是浅比较的,因为深比较的场景是相当昂贵的。所以我们要注意我们在 1.1
中说到的一些注意点:不要直接为 props
设置对象或者数组 、 不要将方法直接绑定在元素上,因为其实函数也是对象。
1.3 Immutable.js
javascript
中的对象一般都是可变的,因为使用了引用赋值,新的对象简单的引用了原始对象,改变新对象将影响到原始对象。
举个例子:
student = {age : 1};
school = student;
school.age = 2;
当我们给 school.age
赋值后,会发现 student.a
也变成了 2,虽然我们可以通过深拷贝与浅拷贝解决这个问题,但是这样做非常的昂贵,对 cpu
和内存会造成浪费。
这里就需要用到 Immutable
,通过Immutable
创建的 Immutable Data
一旦被创建,就不能再更改。对 Immutable
对象进行修改、添加或删除操作,都会返回一个新的 Immutable
对象。
下面是三个比较重要且用到的数据结构
Map
:键值对集合,对应 Object,ES6 中也有专门的 Map 对象List
:有序可重复列表,对应于 ArrayArraySet
:有序且不可重复的列表
我们可以看一个例子:
使用 Map
生成一个 immutable
对象:
import {Map, is} from 'immutable';
let a = Map({
'name': 'apple',
'list': Map({name: 'orange'})
})
let b = a.set('name','banana');
console.log(a.get('course') === b.get('course')); // 返回 true
console.log(a === b); // 返回 false
Immutable.is
比较的是两个对象的 hashCode
或 valueOf
(对于 JavaScript
对象)。由于 immutable
内部使用了 Trie
数据结构来存储,只要两个对象的 hashCode
相等,值就是一样的。这样的算法避免了深度遍历比较,性能非常好。
Immutable
优点:
- 减少内存的使用
- 并发安全
- 降低项目的复杂度
- 便于比较复杂数据,定制
shouldComponentUpdate
方便 - 时间旅行功能
- 函数式编程
Immutable
缺点:
- 学习成本
- 库的大小(建议使用
seamless-immutable
) - 对现有项目入侵严重
- 容易与原生的对象进行混淆
2. 多个 react 组件性能优化
react
组件在装载过程中,react
通过在 render
方法在内存中产生一个树形结构,树上的节点代表一个 react
组件或者原生的 Dom
元素,这个树形结构就是我们所谓的 Vitural Dom
,react
根据这个来渲染产生浏览器的 Dom
树。
react
在更新阶段对比原有的 Vitural Dom
和新生成的 Vitural Dom
,找出不同之处,在根据不同来渲染Dom
树。
react
为了追求高性能,采用了时间复杂度为 O(N)
来比较两个属性结构的区别,因为要确切比较两个树形结构,需要通过O(N^3)
,这会降低性能。
- 节点类型不同
// A 组件
<div>
<Todos />
</div>
// B 组件
<span>
<Todos />
</span>
我们想把 A
组件更新成 B
组件,react
在做比较的时候,发现最外面的根结点完全不一样,直接销毁之前的 <div>
节点,包括里面的子节点也一并销毁,这是一个巨大的浪费,但是为了避免 O(N^3)
的时间复杂度,只能采用这种方式。
所以在开发过程中,我们应该尽量避免上面的情况,不要将包裹节点的类型随意改变。
- 两个节点类型一样
这里包括两种情况,一种是节点是 Dom
类型,还有一种 react
组件。
对于 dom
类型,我们举个例子:
// A 组件
<div style={{color: 'red',fontSize:15}} className="welcome">
Hello World!!!
</div>
// B 组件
<div style={{color: 'green',fontSize:15}} className="react">
Good Bye!!!
</div>
上述 A 和 B 组件的区别是文字、className
、style
中的 color
发生改变,因为 Dom
元素没变,React
只会修改他变化的部分。
针对 react
组件类型,渲染无非就是再执行一遍组件实例的更新过程,最主要的就是定制shouldComponentUpdate
。
- 多个子组件情况
例子一:
// A
<ul>
<TodoItem text="First" complete={false} />
<TodoItem text="Second" complete={false} />
</ul>
// B
<ul>
<TodoItem text="First" complete={false} />
<TodoItem text="Second" complete={false} />
<TodoItem text="Third" complete={false} />
</ul>
从 A 变到 B,如果 shouldComponentUpdate
处理得当,我们只需要更新装载 third
的那 一次 就行。
我们来看看下一个例子:
// A
<ul>
<TodoItem text="First" complete={false} />
<TodoItem text="Second" complete={false} />
</ul>
// B
<ul>
<TodoItem text="Zero" complete={false} />
<TodoItem text="First" complete={false} />
<TodoItem text="Second" complete={false} />
</ul>
这里因为 react
是采用 O(n)
的时间复杂度,所以会依次将 text
为First
的改为 Zero
,text
为Second
改为 First
,在最后再加上一个组件,text
为Second
。现存的两个的 text
的属性都被改变了,所以会依次渲染。
如果我们这里有 100 个实例,那么就会发生 100 次更新。
这里我们就要用到 Key
了
简单来说,其实这一个 Key
就是 react
组件的身份证号。
我们将上一个例子改成如下,就可以避免上面的问题了,react
就能够知道其实 B 里面的第二个和第三个组件其实就是 A 中的第一个和第二个实例。
// A
<ul>
<TodoItem key={1} text="First" complete={false} />
<TodoItem key={2} text="Second" complete={false} />
</ul>
// B
<ul>
<TodoItem key={0} text="Zero" complete={false} />
<TodoItem key={1} text="First" complete={false} />
<TodoItem key={2} text="Second" complete={false} />
</ul>
不过现在,react
也会提醒我们不要忘记使用key
,如果没有加,浏览器中会报错。
关于 key
的使用我们要注意的是,这个 key
值要稳定不变的,就如同 身份证号 对于我们是稳定不变的一样。
一个常见的错误就是,拿数组的的下标值去当做key
,这个是很危险的,代码如下,我们一定要避免
<ul>
{todos.map((item, index) => {
<TodoItem
key={index}
text={item.text}
completed={item.completed}
>
})
}
</ul>
未完待续 …