React.PureComponent全解析

Component,你可不够纯哦

Posted by MeloGuo on February 1, 2019

React.PureComponent

在 v15.3 中增加了一个新 API,从名字可以看出 PureComponent 应该比 Component 更纯。文档中解释,它们的不同之处在于 React.Component 没有实现 shouldComponentUpdate(),但是 React.PureComponent实现了它。采用对属性和状态用浅比较的方式进行更新。

如何浅比较?

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

function shallowEqual (prevVal, newVal) {
  const prevValKeys = Object.keys(prevVal)
  const newValKeys = Object.keys(newVal)

  if (prevValKeys.length !== newValKeys.length) { return false }

  return prevValKeys.every(prevValKey => newValKeys.indexOf(prevValKey) > -1 && prevVal[prevValKey] === newVal[prevValKey])
}

PureComponent 是如何使用 shallowEqual 的?

先看一个真正 PureComponent 的例子:

class App extends React.PureComponent {
  state = {
    items: [1, 2, 3]
  }

  handleClick = () => {
    const { items } = this.state
    items.pop()
    this.setState({ items })
  }

  render () {
    return (
      <div>
        <ul>
          {this.state.items.map((item, index) => <li key={index}>{item}</li>)}
        </ul>
        <button onClick={this.handleClick}>删除</button>
      </div>
    )
  }
}

在这个组件中,你会发现点击删除按钮是无效的!原因就是新的state.item和旧的state.item指向同一个引用,所以被 PureComponent 拒绝了渲染。这也正是 PureComponent 的特性。在 Component 中使用 shallowEqual 这个函数模拟一下便也有这样的效果。

class App extends React.Component {
  state = {
    items: [1, 2, 3]
  }

  // 模拟 PureComponent
  shouldComponentUpdate (nextProps, nextState) {
    return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
  }

  handleClick = () => {
    const { items } = this.state
    items.pop()
    this.setState({ items })
  }

  render () {
    return (
      <div>
        <ul>
          {this.state.items.map((item, index) => <li key={index}>{item}</li>)}
        </ul>
        <button onClick={this.handleClick}>delete</button>
      </div>
    )
  }
}

所以在 PureComponent 中更新引用类型的数据时不要使用同一引用,而应该使用一个新的引用,如下:

handleClick = () => {
    const { items } = this.state
    items.pop()
    // 使用一个新的引用
    this.setState({ items: [...items] })
  }

这样便不会被判定为 state 未改变。但是对于不变的数据却应该使用同一个引用,不要每次 render 都让引用变化引发不必要的重渲染:

update () {
    console.log('update')
}
render () {
    // 每次渲染都会传入一个新的引用的函数
    return <div onClick={this.update.bind(this)} />
}

解决方法便是每次传入相同引用的函数

update = () => {
    console.log('update')
}
render () {
    return <div onClick={this.update} />
}

在处理对象时也有相似的问题,例如为属性指定默认值:

render () {
    // 在 JS 中 {} 不等于 {}
    return <div data={this.state.data || {}} />
}

这种情况每次传入的默认值都会被判定为新值从而进行渲染,所以应该使用一个变量固定引用。

defalutData = {}

render () {
    return <div data={this.state.data || this.defalutData} />
}

总结

PureComponent真正起作用的,只是在一些纯展示组件上,复杂组件用了也没关系,但是在 shadowEqual 那里便会被拦截,不过记得 props 和 state 不能使用同一个引用哦。

著作权声明

本文作者 郭梓梁,首次发布于 MeloGuo Blog,转载请保留以上链接