Kwin Huang

不会搞艺术的程序员不是好设计师.

无状态组件的最佳写法

2018-09-27

默认渲染行为的问题

在React Component的生命周期中,有一个shouldComponentUpdate方法。这个方法默认返回值是true。

这意味着就算没有改变组件的props或者state,也会导致组件的重绘。这就经常导致组件因为不相关数据的改变导致重绘,这极大的降低了React的渲染效率

调用了setState方法后, 会触发组件的render方法, 并且state如果有传给子组件的话也会导致子组件的更新, 但是如果 setState前后组件的state并没有改变的话, 还是会触发组件极其子组件的更新, 这样就造成了性能的浪费, 所以可以使用react提供的 PureComponent组件.

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

class Temp extends PureComponent {
  render() {
    console.log('render Temp');
    return (
      <div>{ this.props.val }</div>
    );
  }
}

class App extends Component {
  state = {
    val: 1
  }

  componentDidMount() {
    setInterval(() => {
      this.setState({
        val: 1
      })
    }, 2000)
  }

  render() {
    console.log('render App');
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <Temp val={ this.state.val } />
      </div>
    );
  }
}

export default App;

如果Temp组件不使用PureComponent组件, 而是使用普通的无状态组件的话, 当父组件的定时器在跑的时候, Temp组件也会不断的render, 造成了性能的浪费. 使用PureComponent后可以看到, Temp组件不会render了

好处: 提升性能, 可以少写shouldComponentUpdate

原理 当组件更新时,如果组件的 props 和 state 都没发生改变, render 方法就不会触发,省去 Virtual DOM 的生成和比对过程,达到提升性能的目的。具体就是 React 自动帮我们做了一层浅比较:

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

而 shallowEqual 又做了什么呢?会比较 Object.keys(state | props) 的长度是否一致,每一个 key 是否两者都有,并且是否是一个引用,也就是只比较了第一层的值,确实很浅,所以深层的嵌套数据是对比不出来的。

易变数据不能使用一个引用

class App extends PureComponent {
  state = {
    items: [1, 2, 3]
  }
  handleClick = () => {
    const { items } = this.state;
    items.pop();
    this.setState({ items });
  }
  render() {
    return (<div>
      <ul>
        {this.state.items.map(i => <li key={i}>{i}</li>)}
      </ul>
      <button onClick={this.handleClick}>delete</button>
    </div>)
  }
}

会发现,无论怎么点 delete 按钮, li 都不会变少,因为 items 用的是一个引用, shallowEqual 的结果为 true 。改正

handleClick = () => {
  const { items } = this.state;
  items.pop();
  this.setState({ items: [].concat(items) });
}

这样每次改变都会产生一个新的数组,也就可以 render 了。

结束语

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