React中最重要的就是组件,写的更多的组件都是继承至 React.Component 。大部分同学可能都会认为 Component 这个base class 给我们提供了各种各样的功能。他帮助我们去运行了这个 render function 。然后最终把我们写在里面的 dom 标签或者子组件之类的渲染出来。渲染到我们的浏览器里面变成想要的页面的一个形式。在真正的看源码之前我也是这么认为的。但是看了源码之后发现他颠覆了我对他的一个认知。
在 React 当中不仅仅只有 Component 这一个 base class 。还有另外一个叫做 PureComponent 。这个跟 Component 他唯一的区别就是他提供了简便的 shouldComponentUpdate 的一个实现。他保证我们的组件在 props 没有任何变化的情况下能够减少必要的更新。
在源码 React.js 里面找到 import {Component, PureComponent} from './ReactBaseClasses'; 得知 Component 来自于 ReactBaseClasses.js 。 打开 ReactBaseClasses.js 。 找到 Component
function Component(props, context, updater) { this.props = props; this.context = context; // If a component has string refs, we will assign a different object later. this.refs = emptyObject; // We initialize the default updater but the real one gets injected by the // renderer. this.updater = updater || ReactNoopUpdateQueue; }
看到 Component 是一个 function 去声明一个类的方式。他接收三个参数,一个是 props, 一个是 context , 一个是 updater 。props 和 context 应该是有用到过的。在代码里面我们可以看到 this.props = props , this.context = context 。在组件内部使用的时候我们就是可以直接去用了。然后继续声明了一个 this.refs = emptyObject 。 使用过 React 当中的 string ref 的同学,就知道这个东西的意义是什么。他最终会把我们想要获取的那个节点的实例挂载在 string ref 上。最后一个 update 就比较好奇了,因为我们并没有在 React.Component 当中使用到过这个对象。那么这个对象具体又有什么意义呢,我们接下去往下看。
Component.prototype.isReactComponent = {}; /** * Sets a subset of the state. Always use this to mutate * state. You should treat `this.state` as immutable. * * There is no guarantee that `this.state` will be immediately updated, so * accessing `this.state` after calling this method may return the old value. * * There is no guarantee that calls to `setState` will run synchronously, * as they may eventually be batched together. You can provide an optional * callback that will be executed when the call to setState is actually * completed. * * When a function is provided to setState, it will be called at some point in * the future (not synchronously). It will be called with the up to date * component arguments (state, props, context). These values can be different * from this.* because your function may be called after receiveProps but before * shouldComponentUpdate, and this new state, props, and context will not yet be * assigned to this. * * @param {object|function} partialState Next partial state or function to * produce next partial state to be merged with current state. * @param {?function} callback Called after state is updated. * @final * @protected */ Component.prototype.setState = function(partialState, callback) { invariant( typeof partialState === 'object' || typeof partialState === 'function' || partialState == null, 'setState(...): takes an object of state variables to update or a ' + 'function which returns an object of state variables.', ); this.updater.enqueueSetState(this, partialState, callback, 'setState'); }; /** * Forces an update. This should only be invoked when it is known with * certainty that we are **not** in a DOM transaction. * * You may want to call this when you know that some deeper aspect of the * component's state has changed but `setState` was not called. * * This will not invoke `shouldComponentUpdate`, but it will invoke * `componentWillUpdate` and `componentDidUpdate`. * * @param {?function} callback Called after update is complete. * @final * @protected */ Component.prototype.forceUpdate = function(callback) { this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); };
我们看到 Component 的 prototype 上面有个 isReactComponent ,他等于一个空对象。这个东西好像没有什么特别的用处,就先略过。
然后下面有个原型上的方法叫做 setState 。那么这个就是我们在 React 当中使用最多的一个api了。他是用来更新我们组件的状态的。那么他接收两个参数,一个是partialState,他就是我们要更新的 state ,可以是个对象,也可以是个方法。在后续的 react 版本中,更推荐使用方法。第二个是个 callback。那么 callback 就是在 state 真正更新完之后他会执行这个 callback 。这个方法里面可以看到 invariant 。 invariant这一段代码他就是一个提醒。他判断一下 partialState 是对象,方法还是 null 。如果都不满足,就会有后续的一个字符串提醒。
这个就是一个不重要的代码。那么重要的是什么呢,重要的是 this.updater.enqueueSetState 。也就是说我们调用 setState ,其实就是调用 enqueueSetState。这个 enqueueSetState 就是我们初始化 Component 的时候传进来的 enqueueSetState 这个方法。这个方式是在 React dom 里面去实现的,跟 React 是没有关系的。为什么要这么去做呢,因为不同的平台,比如 React dom 和 React native 。他们用的核心一模一样的。也就是 Component api 是一样的。但是具体到更新 state 。更新了 state 设计到渲染,那么这个渲染流程是跟平台有关的。react dom 平台跟 react native 平台他实现的方式肯定不一样的。所以这部分作为一个参数让不同的平台传入进来去定制他自己的一个实现方式。这就是 enqueueSetState 要在 setState 里面调用的原因。
接下来原型上还有个方法是 forceUpdate 。这个跟 setState 一样的。也是调用了 updater 里面的一个方法。叫做 enqueueForceUpdate。这个 api 也不是很常用,就是强制 react 去更新一遍,即便你的 state 没有进行一个更新。
/** * Deprecated APIs. These APIs used to exist on classic React classes but since * we would like to deprecate them, we're not going to move them over to this * modern base class. Instead, we define a getter that warns if it's accessed. */ if (__DEV__) { const deprecatedAPIs = { isMounted: [ 'isMounted', 'Instead, make sure to clean up subscriptions and pending requests in ' + 'componentWillUnmount to prevent memory leaks.', ], replaceState: [ 'replaceState', 'Refactor your code to use setState instead (see ' + 'https://github.com/facebook/react/issues/3236).', ], }; const defineDeprecationWarning = function(methodName, info) { Object.defineProperty(Component.prototype, methodName, { get: function() { lowPriorityWarning( false, '%s(...) is deprecated in plain JavaScript React classes. %s', info[0], info[1], ); return undefined; }, }); }; for (const fnName in deprecatedAPIs) { if (deprecatedAPIs.hasOwnProperty(fnName)) { defineDeprecationWarning(fnName, deprecatedAPIs[fnName]); } } }
接下来看到 isMounted 和 replaceState 。这是即将被废弃的两个 api 。这两个 api 因为本身就没有怎么用到,也没有什么特别大的用处。下个版本就没有,所以这段就略过。
到这里我们可以看到 Component 的定义已经结束了。所以这个 base class 只有这么一点东西,没有任何其他含义,也没有任何关于生命周期的相关方法。看到这里肯定非常迷糊,这是没有办法的事情,因为开始看源码的时候我也是抱着 React.Component 也是非常复杂的,他帮我们实现了各种各样的逻辑。但事实上直到后续看 React dom 如何去创建更新以及实现这整个的流程,我才发现这个 Component 只是用来帮助我们去承载一些信息的。
接下来我们看另外一个 base class ,也就是 PureComponent
function ComponentDummy() {} ComponentDummy.prototype = Component.prototype; /** * Convenience component with default shallow equality check for sCU. */ function PureComponent(props, context, updater) { this.props = props; this.context = context; // If a component has string refs, we will assign a different object later. this.refs = emptyObject; this.updater = updater || ReactNoopUpdateQueue; } const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy()); pureComponentPrototype.constructor = PureComponent; // Avoid an extra prototype jump for these methods. Object.assign(pureComponentPrototype, Component.prototype); pureComponentPrototype.isPureReactComponent = true;
那么 PureComponent 我们可以认为他是继承与 Componnet 。他们两个也没有什么可继承的东西,因为他们两接收的东西是一模一样的,都是 props, context, updater 。这里用 ComponentDummy 去实现了一个简单的类似于继承的方式。 PureComponent.prototype 去 new 了一个 ComponentDummy 。 ComponentDummy 是一个空类。 将 Component 的原型赋给了 ComponentDummy 的原型。然后又将 ComponentDummy 的原型 赋给了PureComponent 。pureComponentPrototype 的构造函数指向了 pureComponent 。实际这就是一个实现继承的过程。其实就是一模一样。唯一的区别就是上面加了一个 isPureReactComponent 。他通过这么一个属性来标识继承自这个类的组件,他是一个 PureReactComponent。然后在后续更新的过程当中。 React dom 他会主动的去判断他是不是一个 PureComponent 。然后根据 props 是否更新来判断这个这个组件是否需要更新。