在我們平時寫一個類元件的時候,一般都會繼承一個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全部的分析。