TL;DR
React 15.3.0 新增了一个 PureComponent
类,以 ES2015 class 的方式方便地定义纯组件 (pure component)。这篇文章分析了一下源码实现,并衍生探讨了下 shallowCompare
和 PureRenderMixin
。相关的 GitHub PR 在 这里 。
PureComponent 源码分析
这个类的用法很简单,如果你有些组件是纯组件,那么把继承类从 Component
换成 PureComponent
即可。当组件更新时,如果组件的 props
和 state
都没发生改变,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
出现之前,shallowCompare
和 PureRenderMixin
都可以做一样的事情。于是好奇看了一下后两者的代码。
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
可能更高效。