【React.js 20】React性能优化

2018-04-23  本文已影响108人  IUVO

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}/>//却使用这种方式传递,也是额外造成了开销

这些都是针对单个组件的性能优化的一些点。

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是非常不建议这样的递归对比,非常耗时),所以需要引入:facebookimmutable-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做一个初步的介绍,笔者也没有深入学习,后续详细学习后再出文章讲解吧。

<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之前采用RenderToStringRenderToStaticMarkup,一般用RenderToString,因为它会带上data-reactid等绑定事件时必须的信息。RenderToStaticMarkup如果不在客户端渲染,只需要一个DOM结构也可以使用它,它的体积还会更小一些。
2、React16之后新出了RenderToNodeStream,官方给出的数据是性能提升了3倍,因为它采用了流的模式,向前端一边渲染,一边返回给前端。
3、React16之后还新出了hydrate取代了render,二者其实本质上差不多,都可以使用,只是根据服务端的操作,用hydrate函数在语义上会更贴切,因为其不做渲染,只做添加交互事件。

实际SSR的操作,首先需要把代码打包编译,准备上线。
服务端的node使用babel-node配置node里的react环境。
修改客户端代码,抽离App组件,前后端共享。
服务端生成DOM结构,渲染,加载build后的cssjs

详细的操作步骤,后续再补上。

上一篇下一篇

猜你喜欢

热点阅读