React PureComponent 源码解析

TL;DR

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

PureComponent 源码分析

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

import React, { PureComponent } from 'react'

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

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

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 拷贝过来了。

PureRenderMixin 不一样的是,这里完全没有实现 shouldComponentUpdate。那 PureComponent 的 props/state 比对是在哪里做的呢?答案是 ReactCompositeComponent

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

这个类的代码改动很多,但关键就在 这里 。下面是我简化后的代码片段:

// 定义 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

阅读 16k

推荐阅读

前端,后端,编程技巧,各种被坑经验,无所不包(其实就是想什么放什么)

31 人关注
39 篇文章
专栏主页
目录