React原始碼解析(三):react-component

jiangzhifeng發表於2019-04-04

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



相關文章