# React 性能优化
# 更新原则
React组件,肯定离不开props 和 state,我们可以在props和state中存放任意类型的数据,通过改变props和state,去控制整个组件的状态,去控制整个组件的状态,当props和state发生变化的时候,(即使在render的时候没有改使用state或者props的值
)React都会重新渲染整个组件,
当组件的 props 或 state 变化,React 将会构建新的 virtual DOM,使用 diff 算法把新老的 virtual DOM 进行比较,如果新老 virtual DOM 树不相等则重新渲染,相等则不重新渲染。DOM 操作是非常耗时的,这导致重新渲染也非常的耗时,因此要提高组件的性能就应该尽一切可能的减少组件的重新渲染。
但是我们以为rerender的仅仅更改的dom节点,但事实上react的更新原则是,只要组件的 props 或 state 发生了变化就会重新渲染整个组件,除了必要渲染的三个节点外,还渲染了其他不必要渲染的节点
,这对性能是一个很大的浪费。如果对于复杂的页面,这将导致页面的整体体验效果非常差。因此要提高组件的性能,就应该想尽一切方法减少不必要的渲染
# 组件优化
Pure Component 如果一个组件只和 props 和 state 有关系,给定相同的 props 和 state 就会渲染出相同的结果,那么这个组件就叫做纯组件
shouldComponentUpdate shouldComponentUpdate 这个函数会在组件重新渲染之前调用,函数的返回值确定了组件是否需要重新渲染。函数默认的返回值是 true,意思就是只要组件的 props 或者 state 发生了变化,就会重新构建 virtual DOM,然后使用 diff 算法进行比较,再接着根据比较结果决定是否重新渲染整个组件。函数的返回值为 false 表示不需要重新渲染
# 优化策略1 使用PureRenderMixin
减少不必要渲染
React 官方推荐可以采用PureRenderMixin的方法,这个方法提供一个shouldComponentUpdate的实现,提供一个浅比较
,将当前的props和state与下一个props和state去做比较。
但是,这个仅仅做的是浅比较,如果数据结构比较深层次的话,那么这种方法可能还是会返回false,这是一个错误的结果,我们只能通过调用forceUpate()
去强制更新。
eg1: 使用插件
import PureRenderMixin from 'react-addons-pure-render-mixin';
class FooComponent extends React.Component {
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
render() {
return <div className={this.props.className}>foo</div>;
}
}
eg2: 直接继承类 React.PureComponent,他在内部会实现shouldComponentUpdate
import React from 'react';
class FooComponent extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return <div className={this.props.className}>foo</div>;
}
}
# 优化策略2 使用 immutable objects
对于复杂的数据的比较是非常耗时的,而且可能无法比较,通过使用 Immutable.js 能够很好地解决这个问题,Immutable.js 的基本原则是对于不变的对象返回相同的引用,而对于变化的对象,返回新的引用。因此对于状态的比较只需要使用如下代码即可:
shouldComponentUpdate() {
return ref1 !== ref2;
}
这类比较是非常快速的, 如何在代码中使用Immutable.js 与之带来的问题是:
- 学习Immutable的成本
- 原生js对象和Immutable的交互
# 性能测试
# 测试1: 渲染长列表10000条数据
方式1:使用原生的js 花费时间 15ms 左右的时间
(function() {
console.time("start")
var id = document.getElementById("id");
var items = [];
for(var i = 0; i < 9999; i++) {
items.push(i)
}
items.forEach((index) => {
var newDiv = document.createElement("div");
var textnode = document.createTextNode(index); // Create a text node
newDiv.appendChild(textnode);
id.appendChild(newDiv);
})
document.addEventListener("DOMContentLoaded", function(event) {
console.log("DOM fully loaded and parsed");
console.timeEnd("start")
});
})();
方式2:使用React的Component 及其缓慢,大概需要8000+ms,是原生js的500倍,简直。
import React, { Component } from 'react';
import Item from './Item';
import _ from 'lodash'
class List extends Component {
componentDidMount() {
console.timeEnd("name")
}
render() {
let items = []
items = _.times(10000, (index) => index)
console.time("name")
let showLists = items.map((item) => {
return <Item name={item} key={item}/>
})
return (
<div>
{showLists}
</div>
);
}
}
export default List;
分析:为什么这么慢?
因为实际上与js的原生每一行都是一个<div>
元素不同,这里的每一个Item都是react的一个组件,并且这个组件是被react封装过的组件,里面包含了大量的js逻辑代码和程式,并不像<div>
只是一个元素那么简单。所以想到这里就是一个很有意思的问题,究竟什么样的东西该被封装成一个组件,同时又没有必要去写成一个Full Component,还是说我们仅仅使用stateless funciton就好了呢。下面的例子:我们使用StateLess function会发现,确实性能比使用Full component好了很多。
方式3: 使用React的StateLess function
大概需要 700ms,与上面相比已经提高了很多了
import React, { Component } from 'react';
import NoReactItem from './Item-without-react'
import _ from 'lodash'
class List extends Component {
componentDidMount() {
console.timeEnd("name")
}
render() {
let items = []
items = _.times(10000, (index) => index)
console.time("name")
let showLists = items.map((item) => {
return (
<NoReactItem name={item} key={item}/>
)
})
return (
<div>
{showLists}
</div>
);
}
}
export default List;
import React from 'react';
const NoReactItem = (props) => {
return (
<div>
{props.name}
</div>
)
}
export default NoReactItem;
# 参考
← React 声明式编程 React 实践 →