React 渲染优化:diff 与 shouldComponentUpdate

原文连接:ssshooter.com/2019-03-15-…javascript

我曾经对 shouldComponentUpdate 的用途不解。react 的卖点之一,是经过 diff 虚拟节点树,减小对真实节点的操做,因此我之前觉得既然 diff 了,那就天然知道节点有没有更新了,diff 是根据 setState 的内容进行的,那 shouldComponentUpdate 有什么用呢?html

然而我之前的理解是彻底错误的,形成这个疑问的缘由即是对 React 渲染流程的不熟悉。从头提及。java

setState

你修改了数据,须要 React 从新渲染页面,让你的新数据展现在页面上,须要借助 setState 方法。react

setState 调用后,组件的 render 方法也会自动调用,这就是为何你能在页面看到新数据。可是不管你 setState 修改的是什么,哪怕是页面里没有的一个数据,render 都会被触发,而且父组件渲染中会嵌套渲染自组件。git

class Nest extends React.Component {
  render() {
    console.log('inner')
    return <div>Nest</div>
  }
}

class App extends React.Component {
  render() {
    console.log('outer')
    return (
      <div> <button onClick={() => { this.setState({ anything: 1, }) }} > setState </button> <Nest /> </div>
    )
  }
}
复制代码

因此在这个例子中,点击按钮,即便修改的 anything 根本没有出现,甚至没有定义,render 函数仍是如期运行。每次点击按钮,上面的代码会先输出 outer,而后输出 inner。github

render

render 生成的是什么呢?通常来讲你们都是写 jsx,因此视觉上是一个“dom”,可是实际上,官网也在显眼的位置告诉你,这实际上是一个函数。算法

// jsx
const element = <h1 className="greeting">Hello, world!</h1>
// babel 转换为浏览器能运行的函数
const element = React.createElement(
  'h1',
  { className: 'greeting' },
  'Hello, world!'
)
复制代码

而由于 React 的组件层层嵌套,render 函数会生成一棵描述应用结构的节点树,并保存在内存中。在下一次渲染时,新的树会被生成,接着就是对比两棵树。浏览器

diff

官方一点的定义应该称为 reconciliation,也就是 React 用来比较两棵节点树的算法,它肯定树中的哪些部分须要被更新。babel

在肯定两棵树的区别后,会根据不一样的地方对实际节点进行操做,这样你看到的界面终于在这一步获得了改变。当年 React 也就由于这个高效的 dom 操做方法获得追捧。dom

shouldComponentUpdate

终于说到 shouldComponentUpdate,他是一个组件的方法,用于拦截组件渲染。让咱们用例子解释所谓“拦截渲染”。

class Nest extends React.Component {
  shouldComponentUpdate = () => { // <---- 注意这里
    return false
  }
  render() {
    console.log('inner')
    return <div>Nest</div>
  }
}

class App extends React.Component {
  render() {
    console.log('outer')
    return (
      <div> <button onClick={() => { this.setState({ anything: 1, }) }} > setState </button> <Nest /> </div>
    )
  }
}
复制代码

跟以前的例子差很少,不过当咱们在子组件添加 shouldComponentUpdate 后,再点击按钮,结果是 ————

没错,子组件的渲染函数并无调用,借助 shouldComponentUpdate 返回 false,成功拦截了子组件的渲染。

固然通常不会这么作,由于永远返回 false 的话这个组件(固然由于渲染函数没有运行,因此包括其全部子组件都是不会更新的)就永远不会更新了。

经常使用操做是,在 shouldComponentUpdate 断定该组件的 props 和 state 是否有变化,就像这样:

class Nest extends React.Component {
  shouldComponentUpdate = (nextProps, nextState) => {
    return (
      !shallowEqual(this.props, nextProps) ||
      !shallowEqual(this.state, nextState)
    )
  }
  render() {
    console.log('inner')
    return <div>Nest</div>
  }
}
复制代码

这样能够浅比较 props 和 state 是否有变化,至于为何不深比较?由于那样效率可能会比直接全都运行 render 还低...

由于上面的操做太常见,React 直接为咱们提供了 PureComponent:

class Nest extends React.PureComponent {
  render() {
    console.log('inner')
    return <div>Nest</div>
  }
}
复制代码

使用 PureComponent 的效果就与上面浅比较同样,而且省掉了 shouldComponentUpdate。

何时用?

PureComponent 能提升性能!因此直接用 PureComponent 代替全部 Component!

这固然是错的。

对于明知道须要修改的组件,确定直接返回 false。而可能你没想到,对于明知道须要修改的组件,也请不要使用 PureComponent。

由于正如上面所说,PureComponent 须要进行两次浅比较,而浅比较也是要时间的,如果你明知道这个组件百分百要修改,何须浪费时间去对比呢?

因此 PureComponent 请用在较少进行修改的组件上。

总结

总结一下以上内容,整个流程基本以下:

流程.png

本文部分存在我的理解,若是文中有不严谨的地方,请在评论区指出,谢谢你们的阅读。

参考文献:

reactjs.org/docs/faq-in…

reactjs.org/docs/optimi…

github.com/xitu/gold-m…

cdb.reacttraining.com/react-inlin…

相关文章
相关标签/搜索