美文网首页
【React.js 20】React性能优化

【React.js 20】React性能优化

作者: IUVO | 来源:发表于2018-04-23 15:35 被阅读108次

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

    • 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之前采用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

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

    相关文章

      网友评论

          本文标题:【React.js 20】React性能优化

          本文链接:https://www.haomeiwen.com/subject/ideakftx.html