阅读 916

React性能优化方案之PureComponent

PureComponent介绍

之前的一篇文章React性能优化方案之PureRenderMixin,是对react的shouldComponentUpdate的方法进行重写去优化。但自从React15.3中新加了一个 PureComponent 类,易于在自己的组件使用,只需要将Component 换成 PureComponent 即可,所以原作者建议使用今天要介绍的PureComponent。

PureComponent原理

我们知道组件的state和props的改变就会触发render,但是组件的state和props没发生改变,render就不执行,只有PureComponent检测到state或者props发生变化时,PureComponent才会调用render方法,因此,你不用手动写额外的检查,就可以达到提高性能的而目的。实际上react做了最外层的浅比较:

if (this._compositeType === CompositeTypes.PureClass) {
  shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);
}
复制代码

shadowEqual只会浅检查组件的props和state,所以嵌套对象和数组是不会被比较的。深比较是要逐层遍历枚举对应的键值进行比对,这个操作比较浪费时间。如果比较的深的情况,你也可以使用shouldComponentUpdate来手动单个比较是否需要重新渲染。最简单的方式就是直接比较props或state:

shouldComponentUpdate(nextProps, nextState) {
  return nextProps.xxx.xx === props.xxx.xx;
}
复制代码

另外,你可以使用immutable属性,Immutable.js就是一个Immutable库。这种情况下,属性的比较是非常容易的,因为已存在的对象就不会发生改变,取而代之的是重新创建新的对象。

使用PureComponent

爬坑1:易变数据不能使用一个引用

PureComponent节约了我们的时间,避免了多余的代码。那么,掌握如何正确使用它是非常重要的,否则如果使用不当,它就无法发挥作用。例如,让我们想想这样一种情况,父组件有一个render方法和一个click处理方法:

handleClick() {
  let {items} = this.state

  items.push('new-item')
  this.setState({ items })
}

render() {
  return (
    <div>
      <button onClick={this.handleClick} />
      <ItemList items={this.state.items} />
    </div>
  )
}
复制代码

由于ItemList是一个纯组件,这时候点击它是不会被渲染的,但是我们的确向this.state.items加入了新的值,但是它仍然指向同一个对象的引用。但是,通过移除可变对象就很容易改变这种情况,使之能够正确被渲染。

handleClick() {
  this.setState(prevState => ({
    words: prevState.items.concat(['new-item'])
  }))
}
复制代码

爬坑2:不变数据使用一个引用

上面易变数据不能使用一个引用的案例中有一个点击删除操作,如果我们删除的代码这么写:

constructor(props){
    super(props)
    this.state={
        items: [{a: 1}, {a: 2}, {a: 3}]
    }
}
handleClick = () => {
  const { items } = this.state;
  items.splice(items.length - 1, 1);
  this.setState({ items });
}
复制代码

items 的引用也是改变的,但如果 items 里面是引用类型数据这时候state.items[0] === nextState.items[0]是false,子组件里还是重新渲染了。这样就需要我们保证不变的子组件数据的引用不能改变。这个时候可以使用前面说的immutable-js函数库。

爬坑3:父给子通过props传递回调函数

我们在子父通信传递回调函数时候:

// step1
<MyInput onChange={e => this.props.update(e.target.value)} />
// step2
update(e) {
  this.props.update(e.target.value)
}
render() {
  return <MyInput onChange={this.update.bind(this)} />
}

复制代码

由于每次 render 操作 MyInput 组件的 onChange 属性都会返回一个新的函数,由于引用不一样,所以父组件的 render 也会导致 MyInput 组件的 render ,即使没有任何改动,所以需要尽量避免这样的写法,最好封装:

update = (e) => {
  this.props.update(e.target.value)
}
render() {
  return <MyInput onChange={this.update} />
}
复制代码

爬坑4:新数组,空数组,也会导致组件重新渲染

<Entity values={this.props.values || []}/>
复制代码

为了避免这个问题,你可以使用defaultProps,它包含了一个属性的初始化空状态。在纯组件(PureComponent)被创建时,因为函数的新对象被创建了,所以它会获得新数据,并且重新渲染。解决这个问题最简单的方法就是: 在组件的constructor方法中使用bind。

constructor(props) {
    super(props)
    this.update = this.update.bind(this)
}
update(e) {
    this.props.update(e.target.value)
}
render() {
    return <MyInput onChange={this.update} />
}
复制代码

同时,在JSX中,任何包含子元素(child elements)的组件,shallowEqual检查总会返回false。

PureComponent使用方式

用法很简单:

import React { PureComponent, Component } from 'react';

class Foo extends (PureComponent || Component) {
  //...
}
复制代码

老版本兼容写法,在老版本的 React 里也不会报错的。

总结

1.纯组件忽略重新渲染时,不仅会影响它本身,而且会影响它的说有子元素,所以,使用PureComponent的最佳情况就是展示组件,它既没有子组件,也没有依赖应用的全局状态

2.PureComponent 真正起作用的,只是在一些纯展示组件上,复杂组件用了也没关系,反正 shallowEqual 那一关就过不了,不过记得 props 和 state 不能使用同一个引用。

关注下面的标签,发现更多相似文章
评论