React性能优化
React性能优化主要分三块:
React 组件性能优化
- 属性传递优化
针对单组件性能优化,很多时候其实都是侧重在属性传递优化上,举几个例子:
//不推荐的写法:
//每次render的时候都会执行一次bind,造成额外开销
<button onClick={this.handleClick.bind(this)}>Button1</button>
//每次render的时候都会传入重新生成的一个新的箭头函数对象,也是造成额外开销
<button onClick={()=>this.handleClick()}>Button2</button>
//推荐写法:在构造函数中执行一次bind即可
constructor(props){
super(props)
this.handleClick.bind(this)
}
同理:
//那么,同理每次的render都会重新生成一个info对象,也是额外开销
//不推荐的写法:
<component info={{name:'jack',age:18}}/>
//推荐写法:在构造函数内写:
constructor(props){
super(props)
this.info = {name:'jack',age:18}
}
//render中直接引用
<component info={this.item}/>
或者
//推荐render中使用常量定义
render() {
const item = {name:'jack',age:18}
return (
<component info={item}/>
);
}
还有一点,就是不要传递不需要的属性,尽量有针对性:
//假设state中有很多状态,但是子组件只需要其中一个数据
<Demo {...this.state}/>//却使用这种方式传递,也是额外造成了开销
这些都是针对单个组件的性能优化的一些点。
- 多组件优化
前面讲React生命周期的时候,就提到过,shouldComponentUpdate()
方法是一个可以提供优化的点。举个例子:
class App extends Component {
constructor(props){
super(props)
this.state={
num:1,
title:'title'
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(...args){
this.setState({ num:this.state.num + 1 })
}
render() {
return (
<div>
<h2>App{this.state.num}</h2>
<button onClick={this.handleClick}>Button1</button>
<Demo title={this.state.title}/>
</div>
)
}
}
class Demo extends Component {
render(){
return(
<h2>I'm Demo,{this.props.title}</h2>
)
}
}
上面代码中很容易可以看出来,点击按钮时父组件App
需要重新渲染,因为state.num
改变了,但是子组件Demo
的属性并没有改变,不需要重新渲染,但是也跟着渲染了,这也是一种性能上的浪费。
这里给大家提供一个React16的性能监测的工具,React16
之前,React
自带了一个性能监测的工具,到React16
之后,默认就支持谷歌浏览器自带的性能监测工具,使用起来也很简单:
//浏览器url输入框中,在url后面加上react_pref即可
http://localhost:3000/?react_pref
然后选择开发者工具栏的performance
点击下图所示的圆形的按钮开始记录整个APP执行的性能。 开始记录
我们点击开始记录,点击几次按钮,然后点
stop
停止记录,就会开始生成此次运行的性能数据:性能数据
第一行是运行过程中整个页面的各时间节点运行的
CPU
开销、页面显示等信息;第二行是详细时间开销,重点看
User Timing
,这是我们能操作的部分。放大某个操作对应的渲染改变,你会看到对应的渲染操作耗时例如App [update]
就是App
组件更新渲染所花的时间,Demo [update]
就是Demo
组件更新渲染所花的时间,鼠标悬停在对应事件上就会显示对应时长。
如果在shouldComponentUpdate()
方法对比前后的属性是否有差异来返回是否渲染:
shouldComponentUpdate(nextProps,nextState){
return nextProps.title!==this.props.title
}
再来看看渲染时间:
渲染时间
几乎可以忽略不计了。
但是一般实际开发肯定不会去只比对一个属性,要循环对比,不过React
也提供了一个更好的方法PureComponent
:
class Demo extends React.PureComponent {
render(){
return(
<h2>I'm Demo,{this.props.title}</h2>
)
}
}
不需要任何处理,直接优化,看看性能图:
除了首屏渲染,其他地方,不需要渲染的,直接连经过
shouldComponentUpdate()
方法都没有了。以后如果你的组件只是根据你传进来的值进行渲染,没有内部的状态,我们就可以用
PureComponent
来进行最大限度的优化。
但是,如果要使用shouldComponentUpdate()
方法,如同前面所说,实际项目中很多时候不会只比较一个值,会比较比较复杂的值,例如对象,那么,大家应该知道,在JS
中比较对象,直接用===
来比较的话,实质比较的是内存地址,不会相等的!
这时候,如果去自己定制方法去比较,因为对象层级的原因(例如一个人:{'name':'Mike','car':{'brand':'Benz','model':'C200'}}这样的,那你就要一层层递归的去定制自己的对比方法,而React
是非常不建议这样的递归对比,非常耗时),所以需要引入:facebook
的 immutable-js
包,Github主页可以看这里,它的存在很好的解决了这个问题。
使用immutable-js
:
Map:
immutable-js
提供了Map
用于定义数据对象,is
用于比较两个对象,下面我们来定义两个对象使用看看:
import { Map,is } from 'immutable' //引入Map和is
let obj1 = Map({'name':'Mike','car':Map({'brand':'Benz','model':'C200'})})
let obj2 = Map({'name':'Mike','car':Map({'brand':'Benz','model':'C200'})})
console.log('Map',is(obj1,obj2)) //Map true
let obj3 = {'name':'Mike','car':{'brand':'Benz','model':'C200'}}
let obj4 = {'name':'Mike','car':{'brand':'Benz','model':'C200'}}
console.log('normal',is(obj3,obj4)) //normal false
这样,我们在shouldComponentUpdate()
方法中对复杂对象的判断的时候就改为使用immutable-js
来定义和判断就会简单的多。
immutable-js
的优点:
1、减少内存使用。
2、并发安全。
3、降低项目复杂度。
4、便于比较复杂数据,定制shouldComponentUpdate()
的逻辑。
5、时间旅行功能。
6、函数式编程。
immutable-js
的缺点:
1、学习成本。(无法避免)
2、库的大小。(使用seamless-immutable
,主页点击这里)
3、对现有项目的入侵太严重。(老项目尽量不使用,使用成本太高,需要慎重评估)
这里也只是对immutable-js
做一个初步的介绍,笔者也没有深入学习,后续详细学习后再出文章讲解吧。
- key
如果,我们使用map
来生成标签,不加key
的话,控制台会有警告,这个大家想必都很熟悉了:
<ul>
{this.state.arr.map(v=><li key={v}>{v}</li>)}
</ul>
并且,key
要求也要是唯一的,重复的key
也会导致警告,其实这也是React
的一个优化手段,因为React
对于虚拟DOM是有一个比较的,唯一的key
可以使React
在比较的时候,判断出哪些元素有变动,哪些元素需要新建,哪些不要新建,通过key的比较,就能有效的避免因为位置变动而导致
React`去创建新元素,而仅仅只是需要移动下即可。
Redux 性能优化
Redux
的性能优化这里简单的介绍下reselect
这个库,官方首页看这里。它主要功能是把一些计算给缓存起来,从而实现减少不必要的计算开销而达到性能上的优化。
安装:
npm install reselect --save
首先创建对应的需要数据的selector
const numSelector = createSelector(
state=>state,
//第二个参数是第一个的返回值
state=>({ num : state})
)
修改connect
,把所需的数据包裹在selector
中,传递给connect
,这样就能够对每次计算进行缓存,减少不必要的计算。
@connect(
state=>numSelector(state),
{add, remove, addAsync, addTwice}
)
React 同构
早期的页面渲染,是由服务端请求完数据后和模板拼凑成HTML
发送给前端,前端再进行展示,这样的优点就是首屏很快,但是每次操作都会刷新页面。
后面又出现了浏览器端渲染的模式,通过AJAX
异步获取数据界面数据,部分刷新,不用整屏刷新,这样的好处是体验好,但是因为要先加载JS
文件,在执行JS
方法,首屏的速度就慢了。
二者各有优缺点,所有前后端同构就应运而生了,首屏的时候,依然是在服务端根据数据和模板发送给前端渲染一份出来,但是,后面的交互,采用的还是浏览器端渲染。既能提高首屏速度,又能有较好的交互。
React
是天生支持SSR
的,Node
在服务端渲染首屏,这样首屏渲染的时候,JS
就不需要请求数据了,此时JS
只负责添加事件,从而提高了首屏的速度。
React
同构API:
1、React16
之前采用RenderToString
和RenderToStaticMarkup
,一般用RenderToString
,因为它会带上data-reactid
等绑定事件时必须的信息。RenderToStaticMarkup
如果不在客户端渲染,只需要一个DOM
结构也可以使用它,它的体积还会更小一些。
2、React16
之后新出了RenderToNodeStream
,官方给出的数据是性能提升了3倍,因为它采用了流的模式,向前端一边渲染,一边返回给前端。
3、React16
之后还新出了hydrate
取代了render
,二者其实本质上差不多,都可以使用,只是根据服务端的操作,用hydrate
函数在语义上会更贴切,因为其不做渲染,只做添加交互事件。
实际SSR的操作,首先需要把代码打包编译,准备上线。
服务端的node
使用babel-node
配置node
里的react
环境。
修改客户端代码,抽离App
组件,前后端共享。
服务端生成DOM结构,渲染,加载build
后的css
和js
。
详细的操作步骤,后续再补上。
网友评论