React PureComponent 源码解析

TL;DR

React 15.3.0 新增了一个 PureComponent 类,以 ES2015 class 的方式方便地定义纯组件 (pure component)。这篇文章分析了一下源码实现,并衍生探讨了下 shallowComparePureRenderMixin。相关的 GitHub PR 在 这里html

PureComponent 源码分析

这个类的用法很简单,若是你有些组件是纯组件,那么把继承类从 Component 换成 PureComponent 便可。当组件更新时,若是组件的 propsstate 都没发生改变,render 方法就不会触发,省去 Virtual DOM 的生成和比对过程,达到提高性能的目的。react

import React, { PureComponent } from 'react'

class Example extends PureComponent {
  render() {
    // ...
  }
}

PureComponent 自身的源码也很简单,节选以下:git

function ReactPureComponent(props, context, updater) {
  // Duplicated from ReactComponent.
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

function ComponentDummy() {}
ComponentDummy.prototype = ReactComponent.prototype;
ReactPureComponent.prototype = new ComponentDummy();
ReactPureComponent.prototype.constructor = ReactPureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(ReactPureComponent.prototype, ReactComponent.prototype);
ReactPureComponent.prototype.isPureReactComponent = true;

上面的 ReactPureComponent 就是暴露给外部使用的 PureComponent 。能够看到它只是继承了 ReactComponent 再设定了一下 isPureReactComponent 属性。ComponentDummy 是典型的 JavaScript 原型模拟继承的作法,对此有疑惑的能够看 个人另外一篇文章 。另外,为了不原型链拉长致使方法查找的性能开销,还用 Object.assign 把方法从 ReactComponent 拷贝过来了。github

PureRenderMixin 不同的是,这里彻底没有实现 shouldComponentUpdate。那 PureComponent 的 props/state 比对是在哪里作的呢?答案是 ReactCompositeComponentsegmentfault

ReactCompositeComponent 这个类的信息太少,我只能推测它是负责实际渲染并维护组件实例的对象。建议你们从高层次了解 React 对组件的更新机制便可。如下几篇官方文档看完就足够了。数组

这个类的代码改动不少,但关键就在 这里 。下面是我简化后的代码片断:oop

// 定义 CompositeTypes
var CompositeTypes = {
  ImpureClass: 0,         // 继承自 Component 的组件
  PureClass: 1,           // 继承自 PureComponent 的组件
  StatelessFunctional: 2, // 函数组件
};

// 省略一堆代码,由于加入了 CompositeTypes 形成的调整

// 这个变量用来控制组件是否须要更新
var shouldUpdate = true;

// inst 是组件实例
if (inst.shouldComponentUpdate) {
  shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
} else {
  if (this._compositeType === CompositeType.PureClass) {
    // 用 shallowEqual 对比 props 和 state 的改动
    // 若是都没改变就不用更新
    shouldUpdate =
      !shallowEqual(prevProps, nextProps) ||
      !shallowEqual(inst.state, nextState);
  }
}

简而言之,ReactCompositeComponent 会在 mount 的时候判断各个组件的类型,设定 _compositeType ,而后根据这个类型来判断是非须要更新组件。这个 PR 中大部分改动都是 由于加了 CompositeTypes 而作的调整性工做,实际跟 PureComponent 有关的就是 shallowEqual 的那两行。

关于 PureComponent 的源码分析就到这里。其余的就都是细节和测试,有兴趣的能够本身看看 PR 。

shallowEqual, shallowCompare, PureRenderMixin 的联系

咱们知道在 PureComponent 出现以前,shallowComparePureRenderMixin 均可以作同样的事情。因而好奇看了一下后二者的代码。

shallowCompare 的源码:

var shallowEqual = require('shallowEqual');

function shallowCompare(instance, nextProps, nextState) {
  return (
    !shallowEqual(instance.props, nextProps) ||
    !shallowEqual(instance.state, nextState)
  );
}

module.exports = shallowCompare;

PureRenderMixin 的源码:

var shallowCompare = require('shallowCompare');

var ReactComponentWithPureRenderMixin = {
  shouldComponentUpdate: function(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  },
};

module.exports = ReactComponentWithPureRenderMixin;

能够看到,shallowCompare 依赖 shallowEqual ,作的是跟刚才在 ReactCompositeComponent 里同样的事情 -- 对比 props 和 state 。这个工具函数通常配合组件的 shouldComponentUpdate 一块儿使用,而这就是 PureRenderMixin 作的事情。不过 PureRenderMixin 是配合 React.createClass 这种老的组件定义方式使用的,在 ES2015 class 里用起来不是很方便,这也是 PureComponent 诞生的缘由之一。

最后 shallowEqual 这玩意定义在哪里呢?它其实不是 React 的一部分,而是 fbjs 的一部分。这是 Facebook 内部使用的一个工具集。

小结

React 以前一直没有针对 ES2015 class 的纯组件写法,虽然本身实现起来并不麻烦,但这也算给出了一个官方的解决方案,能够再也不依赖 addon 了。不过 PureComponent 也不是万能的,特定状况下本身实现 shouldComponentUpdate 可能更高效。

参考资料

PureComponent PR
shallowEqual
Shallow Compare
PureRenderMixin

相关文章
相关标签/搜索