React源代碼解析(3):組件的生命週期

這一章可能比較長,由於這一章我會把生命週期,transaction,setState放到一塊兒說明. 組件的生命週期分爲二個部分react

  1. 組件的掛載
  2. 組件的更新

組件的掛載

上一章對於組件的掛載已經作了詳細的說明,可是涉及到組件生命週期部分被略過.接下來我將對其深刻解析. 組件的掛載涉及到二個比較重要的生命週期方法componentWillMountcomponentDidMount.面試

componentWillMount

對於componentWillMount這個函數玩過React的都知道他是組件render以前的觸發. 可是若是我再具體點呢. 是在實例以前?仍是實例以後?仍是構建成真實dom以前?仍是構建成真實dom以前,渲染以前?估計不少人不知道吧.因此在面試的時候不管你對React有多熟,仍是儘可能不要說"精通"二字.(大佬除外)算法

componentWillMount是組件更新以前觸發,因此直接從ReactCompositeComponent.mountComponent裏面找bash

// this.performInitialMount

if (inst.componentWillMount) {
    debugger
    if ("development" !== "production") {
        measureLifeCyclePerf(
            function() {
                return inst.componentWillMount();
            },
            debugID,
            "componentWillMount"
        );
    } else {
        inst.componentWillMount();
    }
    // When mounting, calls to `setState` by `componentWillMount` will set
    // `this._pendingStateQueue` without triggering a re-render.
    if (this._pendingStateQueue) {
        inst.state = this._processPendingState(
            inst.props,
            inst.context
        );
    }
}
複製代碼

代碼在performInitialMount函數裏面,因此在實例以後,虛擬dom構建真實dom以前觸發的架構

componentDidMount

直接看代碼吧app

var markup;
if (inst.unstable_handleError) {
    markup = this.performInitialMountWithErrorHandling(
        renderedElement,
        hostParent,
        hostContainerInfo,
        transaction,
        context
    );
} else {
    markup = this.performInitialMount(
        renderedElement,
        hostParent,
        hostContainerInfo,
        transaction,
        context
    );
}
if (inst.componentDidMount) {
        if ("development" !== "production") {
            transaction
                .getReactMountReady()
                .enqueue(function() {
                    measureLifeCyclePerf(
                        function() {
                            return inst.componentDidMount();
                        },
                        _this._debugID,
                        "componentDidMount"
                    );
                });
        } else {
            transaction
                .getReactMountReady()
                .enqueue(
                    inst.componentDidMount,
                    inst
                );
        }
    }
複製代碼

它是出如今markup(真實dom)以後.可是確定不會在這裏面執行,由於在markup還沒插入到container裏面呢。回顧一下上一章的內容MountComponentIntoNode方法mountComponent以後還有個setInnerHTML(container, markup)只有這個函數執行完以後componentDidMount才能執行.dom

注意performInitialMount方法 看看下面的代碼異步

class A extends React.Component {
    render(){
        return <K />
    }
}
<App>
    <A />
</App>
複製代碼

this.componentDidMount的執行順序是K-->A--->App. 由於APP執行到 this.performInitialMount就開始深度遍歷了.而後執行AA又遍歷執行K. K執行完才向上執行. 瞭解了他們的執行順序咱們看看函數

transaction
    .getReactMountReady()
    .enqueue(function() {
        measureLifeCyclePerf(
            function() {
                return inst.componentDidMount();
            },
            _this._debugID,
            "componentDidMount"
        );
    });
複製代碼

再看看這個transaction是在哪裏生成的oop

var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
    /* useCreateElement */
    !shouldReuseMarkup &&
        ReactDOMFeatureFlags.useCreateElement
);
transaction.perform(
    mountComponentIntoNode,
    null,
    componentInstance,
    container,
    transaction,
    shouldReuseMarkup,
    context
);
複製代碼

transactionReact裏面一個很是核心的功能. 出如今不少個地方,不搞清楚transtion源代碼是沒辦法讀下去的.

事務和隊列

看看官方給出的流程圖

* <pre>
*                       wrappers (injected at creation time)
*                                      +        +
*                                      |        |
*                    +-----------------|--------|--------------+
*                    |                 v        |              |
*                    |      +---------------+   |              |
*                    |   +--|    wrapper1   |---|----+         |
*                    |   |  +---------------+   v    |         |
*                    |   |          +-------------+  |         |
*                    |   |     +----|   wrapper2  |--------+   |
*                    |   |     |    +-------------+  |     |   |
*                    |   |     |                     |     |   |
*                    |   v     v                     v     v   | wrapper
*                    | +---+ +---+   +---------+   +---+ +---+ | invariants
* perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
*                    | |   | |   |   |         |   |   | |   | |
*                    | |   | |   |   |         |   |   | |   | |
*                    | |   | |   |   |         |   |   | |   | |
*                    | +---+ +---+   +---------+   +---+ +---+ |
*                    |  initialize                    close    |
*                    +-----------------------------------------+
 * </pre>
var TransactionImpl = {
  reinitializeTransaction: function () {
    this.transactionWrappers = this.getTransactionWrappers();
    if (this.wrapperInitData) {
      this.wrapperInitData.length = 0;
    } else {
      this.wrapperInitData = [];
    }
    this._isInTransaction = false;
  },

  _isInTransaction: false,

  getTransactionWrappers: null,

  isInTransaction: function () {
    return !!this._isInTransaction;
  },

  perform: function (method, scope, a, b, c, d, e, f) {
    !!this.isInTransaction() ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Transaction.perform(...): Cannot initialize a transaction when there is already an outstanding transaction.') : _prodInvariant('27') : void 0;
    var errorThrown;
    var ret;
    try {
      this._isInTransaction = true;
      errorThrown = true;
      this.initializeAll(0);
      ret = method.call(scope, a, b, c, d, e, f);
      errorThrown = false;
    } finally {
      try {
        if (errorThrown) {
          try {
            this.closeAll(0);
          } catch (err) {}
        } else {
          this.closeAll(0);
        }
      } finally {
        this._isInTransaction = false;
      }
    }
    return ret;
  },

  initializeAll: function (startIndex) {
    var transactionWrappers = this.transactionWrappers;
    for (var i = startIndex; i < transactionWrappers.length; i++) {
      var wrapper = transactionWrappers[i];
      try {
        this.wrapperInitData[i] = OBSERVED_ERROR;
        this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
      } finally {
        if (this.wrapperInitData[i] === OBSERVED_ERROR) {
          try {
            this.initializeAll(i + 1);
          } catch (err) {}
        }
      }
    }
  },

  closeAll: function (startIndex) {
    !this.isInTransaction() ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Transaction.closeAll(): Cannot close transaction when none are open.') : _prodInvariant('28') : void 0;
    var transactionWrappers = this.transactionWrappers;
    for (var i = startIndex; i < transactionWrappers.length; i++) {
      var wrapper = transactionWrappers[i];
      var initData = this.wrapperInitData[i];
      var errorThrown;
      try {
        errorThrown = true;
        if (initData !== OBSERVED_ERROR && wrapper.close) {
          wrapper.close.call(this, initData);
        }
        errorThrown = false;
      } finally {
        if (errorThrown) {
          try {
            this.closeAll(i + 1);
          } catch (e) {}
        }
      }
    }
    this.wrapperInitData.length = 0;
  }
};

module.exports = TransactionImpl;
複製代碼

Transaction的主要做用就是包裝一個函數,函數的執行交給Transaction,同時Transaction會在函數執行先後執行被注入的Wrappers,一個Wrapper有二個方法initializecloseWrapper是經過getTransactionWrappers方法注入的

代碼很簡單,很容易看明白我就具體說明下每一個函數和關鍵屬性的做用

  1. perform執行注入的函數fnwrappers,執行順序爲initializeAll--> fn -->closeAll
  2. initializeAll執行全部Wrapperinitialize方法
  3. closeAll執行全部Wrapperclose方法
  4. reinitializeTransaction初始化
  5. isInTransaction 判斷事務是否在執行

瞭解了Transaction咱們再來仔細分析下上面的代碼

var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
    /* useCreateElement */
    !shouldReuseMarkup &&
        ReactDOMFeatureFlags.useCreateElement
);
複製代碼

ReactReconcileTransactiontransition作了一成包裝

ReactReconcileTransaction

var TRANSACTION_WRAPPERS = [
    SELECTION_RESTORATION,
    EVENT_SUPPRESSION,
    ON_DOM_READY_QUEUEING
];

if ("development" !== "production") {
    TRANSACTION_WRAPPERS.push({
        initialize:
            ReactInstrumentation.debugTool.onBeginFlush,
        close: ReactInstrumentation.debugTool.onEndFlush
    });
}

function ReactReconcileTransaction(useCreateElement) {
    this.reinitializeTransaction();
    this.renderToStaticMarkup = false;
    this.reactMountReady = CallbackQueue.getPooled(
        null
    );
    this.useCreateElement = useCreateElement;
}
var Mixin = {
    getTransactionWrappers: function() {
        return TRANSACTION_WRAPPERS;
    },
    getReactMountReady: function() {
        return this.reactMountReady;
    },
    getUpdateQueue: function() {
        return ReactUpdateQueue;
    },
    checkpoint: function() {
        // reactMountReady is the our only stateful wrapper
        return this.reactMountReady.checkpoint();
    },
    rollback: function(checkpoint) {
        this.reactMountReady.rollback(checkpoint);
    },
    destructor: function() {
        CallbackQueue.release(this.reactMountReady);
        this.reactMountReady = null;
    }
};
複製代碼

getTransactionWrappers方法裏面返回的是TRANSACTION_WRAPPERS他的值有4個也就是說注入了四個Wrapper。具體看看ON_DOM_READY_QUEUEING這個Wraper;

var ON_DOM_READY_QUEUEING = {
    /**
     * Initializes the internal `onDOMReady` queue.
     */
    initialize: function() {
        this.reactMountReady.reset();
    },

    /**
     * After DOM is flushed, invoke all registered `onDOMReady` callbacks.
     */
    close: function() {
        this.reactMountReady.notifyAll();
    }
};
複製代碼

this.reactMountReady是一個隊列, 在組件構建真實dom以後

transaction
    .getReactMountReady()
    .enqueue(function() {
        measureLifeCyclePerf(
            function() {
                return inst.componentDidMount();
            },
            _this._debugID,
            "componentDidMount"
        );
    });
複製代碼

會將componentDidMount方法push進入隊列裏面. 而mountComponentIntoNode(插入到了document中了)執行完畢以後會執行ON_DOM_READY_QUEUEING.close方法也就是this.reactMountReady.notifyAll()方法,釋放隊列中全部的元素。

componentDidMount是經過一個隊列來維護的,由於隊列是先進先出的.而最裏層的組件是最新執行!

組件的更新this.setState

先看看下面二段代碼, console.log(this.props.name, this.state.k)輸入結果是什麼?會輸出二次嗎?爲何?

class Child extends React.Component {
    state = {
        k:null
    }
    render(){
        console.log(this.props.name, this.state.k) 
        return (<div onClick={() => {
            this.setState({ k:12})             // (1)
            this.props.onChange("leiwuyier");  // (2)
        }}>
            child
        </div>)
    }
}
class App extends React.Component {
    state = {
        name:"leiwuyi"
    }
    render(){
        return (
            <div>
                <Child name={this.state.name} onChange={(name) => {
                    this.setState({
                        name
                    })
                }}></Child>
            </div>
        )
    }
}
複製代碼

若是把(1)(2)調換位置呢?輸出的結果又什麼怎麼樣的呢? 答案就是隻會輸出一次"leiwuyi",12.

  1. 由於setState()是異步的,因此(1)(2)調換位置沒什麼區別.
  2. 只更新一次緣由是App先更新,更新的過程當中會將Child實例(指的是instance)屬性的updateBatchNumber設置爲null因此Child組件不會獨自更新一次;

帶着這二個問題來看this.setState()的代碼

ReactComponent.prototype.setState = function(partialState,callback) {
    ...
    ...
    this.updater.enqueueSetState(this, partialState);
                       
};
複製代碼

this.updater是在實例的時候被賦值的.

function ReactComponent(props, context, updater) {
    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;
}
複製代碼

上一章說過. 實例是執行在ReactCompositeComponent.mountComponent

var updateQueue = transaction.getUpdateQueue();
    // Initialize the public class
    var doConstruct = shouldConstruct(Component);
    var inst = this._constructComponent(
        doConstruct,
        publicProps,
        publicContext,
        updateQueue
    );
複製代碼

最終追蹤到getUpdateQueue方法是在ReactUpdateQueue類裏面

enqueueSetState: function( publicInstance,partialState ) {
    if ("development" !== "production") {
        ReactInstrumentation.debugTool.onSetState();
        "development" !== "production"
            ? warning(
                  partialState != null,
                  "setState(...): You passed an undefined or null state object; " +
                      "instead, use forceUpdate()."
              )
            : void 0;
    }

    var internalInstance = getInternalInstanceReadyForUpdate(
        publicInstance,
        "setState"
    );

    if (!internalInstance) {
        return;
    }

    var queue =
        internalInstance._pendingStateQueue ||
        (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    enqueueUpdate(internalInstance);
},

複製代碼

首先拿到實例internalInstance(上一章說到過的具備mountComponent方法的那個實例) 而後將state存到一個隊列queue裏面. 接下來看看enqueueUpdate方法

var ReactDefaultBatchingStrategy = {
    isBatchingUpdates: false,
    batchedUpdates: function(callback, a, b, c, d, e) {
        var alreadyBatchingUpdates =
            ReactDefaultBatchingStrategy.isBatchingUpdates;
        ReactDefaultBatchingStrategy.isBatchingUpdates = true;
        if (alreadyBatchingUpdates) {
            return callback(a, b, c, d, e);
        } else {
            return transaction.perform();  
        }
    }
};

function enqueueUpdate(component) {
    ensureInjected();
    if (!batchingStrategy.isBatchingUpdates) {  
        batchingStrategy.batchedUpdates(
            enqueueUpdate,
            component
        );
        return;
    }
    dirtyComponents.push(component);
    if (component._updateBatchNumber == null) {
        component._updateBatchNumber =
            updateBatchNumber + 1;
    }
}
複製代碼

batchingStrategy.isBatchingUpdates是控制組件的更新. 合成事件那塊有時間我會新開一個章詳細講解。

onClick={() => {this.setState({});console.log(1)}}
複製代碼

點擊以後其實會執行batchingStrategy.batchedUpdates()方法,因爲isBatchingUpdates爲false因此最終執行的是

transaction.perform(() => {this.setState({}));console.log(1)})
複製代碼

執行以後.isBatchingUpdates被設置爲true 前面對事務說的很清楚了.

// 這是注入的二個warpper
var RESET_BATCHED_UPDATES = {
    initialize: emptyFunction,
    close: function() {
        ReactDefaultBatchingStrategy.isBatchingUpdates = false;
    }
};
var FLUSH_BATCHED_UPDATES = {
    initialize: emptyFunction,
    close: ReactUpdates.flushBatchedUpdates.bind(
        ReactUpdates
    )
};
// 因此執行順序是
 FLUSH_BATCHED_UPDATES.initialize()
 RESET_BATCHED_UPDATES.initialize()
 this.setState({});console.log(1);
 FLUSH_BATCHED_UPDATES.close()
 RESET_BATCHED_UPDATES.close()
複製代碼

isBatchingUpdatestrue了因此this.setState執行的dirtyComponents.push(component),push以後 this.setState({})也就執行完了,而後執行console.log(1);最後經過FLUSH_BATCHED_UPDATES.close更新組件.

在事件函數裏面的this.setState()的isBatchingUpdates爲true,因此只會放入dirtyComponents,函數執行完畢,纔會更新組件。這就是解釋了this.setState爲何是異步的緣由

updateComponent

// 有了dirtyComponents以後
for (var i = 0; i < len; i++) {
    var component = dirtyComponents[i];
    ReactReconciler.performUpdateIfNecessary(
        component,
        transaction.reconcileTransaction,
        updateBatchNumber
    );
    ==>
    performUpdateIfNecessary: function(internalInstance, transaction, updateBatchNumber) {
        // 做了一層判斷,爲何Child不會獨自更新一次,緣由就在這裏
        if ( internalInstance._updateBatchNumber !== updateBatchNumber) {
            return;
        }
        internalInstance.performUpdateIfNecessary(
            transaction
        );
    }
    ==> 
    updateComponent(){
        // 執行componentWillReceiveProps方法
        if (
            willReceive &&
            inst.componentWillReceiveProps
        ) {
            if ("development" !== "production") {
                measureLifeCyclePerf(
                    function() {
                        return inst.componentWillReceiveProps(
                            nextProps,
                            nextContext
                        );
                    },
                    this._debugID,
                    "componentWillReceiveProps"
                );
            } else {
                inst.componentWillReceiveProps(
                    nextProps,
                    nextContext
                );
            }
        }
        
        // 合併state
        var nextState = this._processPendingState(
            nextProps,
            nextContext
        );
        
        // 執行shouldComponentUpdate
        var shouldUpdate = true;
        if (!this._pendingForceUpdate) {
            if (inst.shouldComponentUpdate) {
                if ("development" !== "production") {
                    shouldUpdate = measureLifeCyclePerf(
                        function() {
                            return inst.shouldComponentUpdate(
                                nextProps,
                                nextState,
                                nextContext
                            );
                        },
                        this._debugID,
                        "shouldComponentUpdate"
                    );
                } else {
                    shouldUpdate = inst.shouldComponentUpdate(
                        nextProps,
                        nextState,
                        nextContext
                    );
                }
            } 
            ..
        }   
        // 更新組件
        if (shouldUpdate) {
            this._performComponentUpdate(
                nextParentElement,
                nextProps,
                nextState,
                nextContext,
                transaction,
                nextUnmaskedContext
            );
        }
    }
}
複製代碼

注意合併state是在何時,是在componentWillReceiveProps以後shouldComponentUpdate以前進行的. 合併state以後是不能再進行setState()操做的.由於合併之的後_pendingStateQueuenull,再這以後使用setState()會將_pendingStateQueue設置爲true_pendingStateQueuetrue就會又一次執行updateComponent無限循環下去, 這解釋了shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate裏面不能作this.setState操做.

那爲何在componentWillReceiveProps裏面能夠進行setState()操做componentWillReceiveProps的時候不也是爲true嗎?由於componentWillReceiveProps有作

// 組件內部的this.setState,prevParentElement與nextParentElement是相等的. 因此willReceive爲false不會再循環執行componentWillReceiveProps了
if (prevParentElement !== nextParentElement) {
    willReceive = true;
}

複製代碼

流程圖以下

處理了componentWillReceivePropsshouldComponentUpdate這二個生命週期以後而後對組件進行更新this._performComponentUpdate

this._performComponentUpdate

if (inst.componentWillUpdate) {
        if ("development" !== "production") {
            measureLifeCyclePerf(
                function() {
                    return inst.componentWillUpdate(
                        nextProps,
                        nextState,
                        nextContext
                    );
                },
                this._debugID,
                "componentWillUpdate"
            );
        } else {
            inst.componentWillUpdate(
                nextProps,
                nextState,
                nextContext
            );
        }
    }
this._updateRenderedComponent(
    transaction,
    unmaskedContext
);

if (hasComponentDidUpdate) {
    if ("development" !== "production") {
        transaction
            .getReactMountReady()
            .enqueue(function() {
                measureLifeCyclePerf(
                    inst.componentDidUpdate.bind(
                        inst,
                        prevProps,
                        prevState,
                        prevContext
                    ),
                    _this2._debugID,
                    "componentDidUpdate"
                );
            });
    } else {
        transaction
            .getReactMountReady()
            .enqueue(
                inst.componentDidUpdate.bind(
                    inst,
                    prevProps,
                    prevState,
                    prevContext
                ),
                inst
            );
    }
}

複製代碼

是否是感到很是眼熟,跟組件的掛載很是相似, 先執行componentWillUpdate方法而後經過_updateRenderedComponent遞歸的更新組件,更新完成以後執行transaction裏面的Wrapper中的close方法, close將釋放componentDidUpdate的隊列.

說到這裏,組件的生命週期也就是講完了. 還有三個比較核心的點.

  1. diff算法 (同級之間的比較,更新先後的虛擬dom究竟是如何對比的)
  2. 事件系統, (React合成系統究竟是什麼?)
  3. fiber架構 (React16版本革命性的變革)
相關文章
相關標籤/搜索