TL;DR
@H_404_2@React 15.3.0 新增了一个PureComponent
类,以 ES2015 class 的方式方便地定义纯组件 (pure component)。这篇文章分析了一下源码实现,并衍生探讨了下 shallowCompare
和 PureRenderMixin
。相关的 GitHub PR 在 这里 。
PureComponent 源码分析
@H_404_2@这个类的用法很简单,如果你有些组件是纯组件,那么把继承类从Component
换成 PureComponent
即可。当组件更新时,如果组件的 props
和 state
都没发生改变,render 方法就不会触发,省去 Virtual DOM 的生成和比对过程,达到提升性能的目的。
import React,{ PureComponent } from 'react' class Example extends PureComponent { render() { // ... } }@H_404_2@
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;@H_404_2@上面的
ReactPureComponent
就是暴露给外部使用的 PureComponent
。可以看到它只是继承了 ReactComponent
再设定了一下 isPureReactComponent
属性。ComponentDummy
是典型的 JavaScript 原型模拟继承的做法,对此有疑惑的可以看 我的另一篇文章 。另外,为了避免原型链拉长导致方法查找的性能开销,还用 Object.assign
把方法从 ReactComponent
拷贝过来了。
@H_404_2@跟 PureRenderMixin
不一样的是,这里完全没有实现 shouldComponentUpdate
。那 PureComponent
的 props/state 比对是在哪里做的呢?答案是 ReactCompositeComponent
。
@H_404_2@ReactCompositeComponent
这个类的信息太少,我只能推测它是负责实际渲染并维护组件实例的对象。建议大家从高层次了解 React 对组件的更新机制即可。以下几篇官方文档看完就足够了。
- @H_404_2@Advanced Performance
- @H_404_2@Reconciliation
- @H_404_2@React (Virtual) DOM Terminology
// 定义 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); } }@H_404_2@简而言之,
ReactCompositeComponent
会在 mount 的时候判断各个组件的类型,设定 _compositeType
,然后根据这个类型来判断是非需要更新组件。这个 PR 中大部分改动都是 因为加了 CompositeTypes
而做的调整性工作,实际跟 PureComponent
有关的就是 shallowEqual
的那两行。
@H_404_2@关于 PureComponent
的源码分析就到这里。其他的就都是细节和测试,有兴趣的可以自己看看 PR 。
shallowEqual,shallowCompare,PureRenderMixin 的联系
@H_404_2@我们知道在PureComponent
出现之前,shallowCompare
和 PureRenderMixin
都可以做一样的事情。于是好奇看了一下后两者的代码。
@H_404_2@shallowCompare
的源码:
var shallowEqual = require('shallowEqual'); function shallowCompare(instance,nextProps,nextState) { return ( !shallowEqual(instance.props,nextProps) || !shallowEqual(instance.state,nextState) ); } module.exports = shallowCompare;@H_404_2@
PureRenderMixin
的源码:
var shallowCompare = require('shallowCompare'); var ReactComponentWithPureRenderMixin = { shouldComponentUpdate: function(nextProps,nextState) { return shallowCompare(this,nextState); },}; module.exports = ReactComponentWithPureRenderMixin;@H_404_2@可以看到,
shallowCompare
依赖 shallowEqual
,做的是跟刚才在 ReactCompositeComponent
里一样的事情 -- 对比 props 和 state 。这个工具函数一般配合组件的 shouldComponentUpdate
一起使用,而这就是 PureRenderMixin
做的事情。不过 PureRenderMixin
是配合 React.createClass
这种老的组件定义方式使用的,在 ES2015 class 里用起来不是很方便,这也是 PureComponent
诞生的原因之一。
@H_404_2@最后 shallowEqual
这玩意定义在哪里呢?它其实不是 React 的一部分,而是 fbjs 的一部分。这是 Facebook 内部使用的一个工具集。
小结
@H_404_2@React 之前一直没有针对 ES2015 class 的纯组件写法,虽然自己实现起来并不麻烦,但这也算给出了一个官方的解决方案,可以不再依赖 addon 了。不过PureComponent
也不是万能的,特定情况下自己实现 shouldComponentUpdate
可能更高效。
参考资料
@H_404_2@PureComponent PRshallowEqual
Shallow Compare
PureRenderMixin