zp1112 / blog

地址
http://issue.suzper.com/
36 stars 3 forks source link

记一次react渲染机制和不可变数据的理解 #7

Open zp1112 opened 6 years ago

zp1112 commented 6 years ago

React渲染

花了一天时间,看了很多文章,大体上是理解了react的render机制,以及不可变数据在render机制中起到的作用。

工具

安装一个工具可以检测组件是否发生了不必要的渲染。如果发生了不必要的组件渲染,控制台会精确定位并且打印出那些状态触发了不必要的渲染。然后针对性的进行优化,简直是神器啊。

npm i -D why-did-you-update

在index.js加

if (process.env.NOED_ENV !== 'production') {
  const { whyDidYouUpdate } = require('why-did-you-update');
  whyDidYouUpdate(React);
}

正常渲染

class App extends component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
      user: {
        name: 'zp'
      }
    }
  }
  handleClick = () => {
    this.setState({
      count: 0
    });
  }
  render() {
    return (
      <div>
        <div onClick={this.handleClick}>点击我</div>
        <div>{this.state.count}</div>
      </div>
    );
  }
}

保存文件,打开页面,点击点击我,会发现控制台打印出了一个console,提示这个render是不必要的render,count并没有改变,但是组件却重新渲染了。

image

使用PureComponent

PureComponent是react自带的继承了component的类,但是在组件re-render的时候加了一层判断,浅比较新的状态和旧的状态是否一致,如果新旧状态的引用不一致,则比较第一层状态值是否改变,上面的例子中count为第一层,值没有改变,所以不重新渲染。

class App extends PureComponent {
// ...代码
}

为什么说是浅比较,那是因为假设我现在改更新的是user里面的name属性,name我们看看即便加了purecomponent,是否还会发生不必要的渲染呢。

修改handleClick

handleClick = () => {
    this.setState({
      user: {
        name: 'zp'
      }
    });
  }

此时页面console,依然打印出不必要渲染,告知这里的不必要渲染造成的原因是user的引用改变了。

由于purecomponent只比较第一层,所以第二层的user下面的值{name: 'zp'}是个引用。引用改变了,值就不一样了,就会触发re-render。相当于

image

deepEquals = (obj1, obj2) => { 
    if (obj1 === obj2) {
      return true;
    }
    for (const key of Object.keys(obj2)) {
      if (obj1.hasOwnProperty(key) && obj1[key] === obj2[key]) {
        return true;
      }
    }
    return false;
  }
  shouldComponentUpdate = (nextProps, nextState) => {
    return  !this.deepEquals(this.state, nextState) || !this.deepEquals(this.props, nextProps);
  }

手写shouldComponentUpdate和加purecomponent的效果是一样的,只能浅比较。

所以purecomponent解决了浅层次的dom渲染优化,但是对于结构更加复杂的嵌套的数据结构,还是会发生不必要的渲染。

immutable.js

网上很火的数据结构immutable.js,似乎很好的解决了这个问题,让我们看看如何简单的使用immutable.js改造上面的例子

首先本地安装immutable.js。

npm i --save immutable

这里我们先把state改造一下,使用immutable的API改造handleClick,最后render里面的dom渲染再使用API改造一下

this.state = {
      data: Immutable.fromJS({
        count: 0,
        user: {
          name: 'zp'
        }
      })
    }

handleClick = () => {
    this.setState((d) => {
      return {
        data: d.data.updateIn(['user', 'name'], () => 'zp') // 这里改变了user里面的name,深层改变
      }
    })
  }

render() {
    const data = this.state.data;
    return (
      <div>
        <div onClick={this.handleClick}>点击我</div>
        <div>{data.get('count')}</div>
        <div>{data.getIn(['user', 'name'])}</div>
      </div>
    );
  }

保存刷新页面,点击,会发现并没有多余的render。cool!

用不用

我觉得这样看来,immutable.js真的很强大很诱人,看到没有浪费的render,这让有强迫症的开发人员感到很舒服,但是网上看到immutable.js的弊端也有些,

  1. ImmutableJS 库体积比较大,大概56k,开启 gzip 压缩后16k。
  2. 学习成本。

如果不用ImmutableJS ,那么就要尽量避免使用复杂的结构,最好扁平化数据结构,但是。。。。那是不可能的!!所以,你们看着办吧。

我只想说,虽然我现在的项目并没有使用react,甚至我也没用过ImmutableJS ,但是我对react的爱是不变的,我会保持学习,一直进步。。。。嘿,小哥,招前端吗?