在我们平时写一个类组件的时候,一般都会继承一个React.Component这个基类,我们可能会觉得,这个baseClass可能封装各种各样的功能(钩子函数等等),它帮助我们运行render函数,然后最终不是我们写在里面的dom标签、子组件之类的把它们都渲染到浏览器里的这种形式。但实际是这样的吗?答案是否定的。
在react当中不止有Component这一个baseClass, 它还有一个PureComponent这个baseClass, 它们俩唯一的区别就是PureComponent提供了一个shouldComponentUpdate简单的实现,在props没有变化的情况下减少不必要的更新。
我们先看看这个Component和PureComponent的实现源码:
/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */import invariant from 'shared/invariant';import lowPriorityWarning from 'shared/lowPriorityWarning';import ReactNoopUpdateQueue from './ReactNoopUpdateQueue';const emptyObject = {};if (__DEV__) { Object.freeze(emptyObject);}/** * Base class helpers for the updating state of a 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.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');};/** * 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]); } }}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;export {Component, PureComponent};复制代码
我们可以看到Component是一个函数,是一个使用函数进行的类声明的一个方式。它接受三个参数props,context,updater,props和context在使用中大家都知道了,那么这个updater什么呢?我们往下看。
我们看到Component原型上挂在了一个方法setState,这个使我们在使用react时最常用的api了,是用来更新我们组件的状态的。它接受两个参数partial和callback,partial可以是一个对象或者一个方法,在后面的版本都推荐使用方法,callback就是在我们这个state更新完之后它会执行callback。
我们可以看到在这个setState方法里,前半部分都是一个提醒,来判断partial这个是否符合预期要求,如果不符合就给出一个错误的提示。重要的是最后这一行代码this.updater.enqueueSetState(this, partialState, callback, 'setState') 其实我们在调用setState方法时,在Component这个对象里面它什么都没有做,它只是调用了Component初始化时传入的updater对象下的enqueueSetState这个方法。enqueueSetState这个方法我们先不讲,它是在react-dom里面实现的,跟react的代码是没有任何关系的。为什么要这么去做呢,因为不用的平台react-native和react-dom它们用的核心是一样的,也就是它们Component的api是一模一样的。但是具体的我们更新state走的流程就是这个具体的渲染的流程是跟平台有关的。react-dom平台和react-native平台它们的实现方式是不一样的,所以这一部分它作为一个参数让不同的平台去传入进来,去定制它的一个实现方法。所以,这就是为什么要使用一个对象传入进来之后在setState里面去调用的原因。具体的实现会在后面的文章中讲道。
加下去我们还会看到Component上还有一个方法叫forceUpdate, 它内部也是调用了this.updater.enqueueSetState(this, callback, 'forceUpdate')这个方法。这个api我们不常用,它的作用就是强制我们Component去更新一遍,即便我们的state没有更新。
然后再往下就是两个即将废弃的api,isMounted 和 replaceState。
至此就是Component定义的全部的内容,没有任何关于生命周期的一些方法,是不是和你想的不一样。
接下来我们来看一下另一个baseClass,它就是PureComponent。我们可以认为它是继承Component,它们接受的东西都是一样的。唯一的区别就是它在PureComponent上加了一个isPureReactComponent, 通过这么一个属性来标识继承自这个类的组件是一个PureComponent。然后在后续的更新当中,react-dom它会主动的去判断它是不是一个PureComponent, 然后根据props是否更新来判断这个组件是否更新。
以上就是对Component和PureComponent全部的分析。