這一章可能比較長,由於這一章我會把生命週期
,transaction
,setState
放到一塊兒說明. 組件的生命週期分爲二個部分react
在上一章對於組件的掛載已經作了詳細的說明,可是涉及到組件生命週期部分被略過.接下來我將對其深刻解析. 組件的掛載涉及到二個比較重要的生命週期方法componentWillMount
和componentDidMount
.面試
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
就開始深度遍歷了.而後執行A
,A
又遍歷執行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
);
複製代碼
transaction
是React
裏面一個很是核心的功能. 出如今不少個地方,不搞清楚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
有二個方法initialize
和close
。Wrapper
是經過getTransactionWrappers
方法注入的
代碼很簡單,很容易看明白我就具體說明下每一個函數和關鍵屬性的做用
perform
執行注入的函數fn
和wrappers
,執行順序爲initializeAll
--> fn
-->closeAll
initializeAll
執行全部Wrapper
的initialize
方法closeAll
執行全部Wrapper
的close
方法reinitializeTransaction
初始化isInTransaction
判斷事務是否在執行瞭解了Transaction
咱們再來仔細分析下上面的代碼
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
/* useCreateElement */
!shouldReuseMarkup &&
ReactDOMFeatureFlags.useCreateElement
);
複製代碼
ReactReconcileTransaction
對transition
作了一成包裝
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
.
setState()
是異步的,因此(1)
和(2)
調換位置沒什麼區別.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()
複製代碼
isBatchingUpdates
爲true
了因此this.setState
執行的dirtyComponents.push(component)
,push
以後 this.setState({})
也就執行完了,而後執行console.log(1)
;最後經過FLUSH_BATCHED_UPDATES.close
更新組件.
在事件函數裏面的
this.setState()的isBatchingUpdates爲true
,因此只會放入dirtyComponents
,函數執行完畢,纔會更新組件。這就是解釋了this.setState
爲何是異步的緣由
// 有了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()
操做的.由於合併之的後_pendingStateQueue
爲null
,再這以後使用setState()
會將_pendingStateQueue
設置爲true
,_pendingStateQueue
爲true
就會又一次執行updateComponent
無限循環下去, 這解釋了shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate
裏面不能作this.setState
操做.
那爲何在componentWillReceiveProps
裏面能夠進行setState()
操做componentWillReceiveProps
的時候不也是爲true
嗎?由於componentWillReceiveProps
有作
// 組件內部的this.setState,prevParentElement與nextParentElement是相等的. 因此willReceive爲false不會再循環執行componentWillReceiveProps了
if (prevParentElement !== nextParentElement) {
willReceive = true;
}
複製代碼
流程圖以下
處理了componentWillReceiveProps
和shouldComponentUpdate
這二個生命週期以後而後對組件進行更新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
的隊列.
說到這裏,組件的生命週期也就是講完了. 還有三個比較核心的點.
diff
算法 (同級之間的比較,更新先後的虛擬dom
究竟是如何對比的)React
合成系統究竟是什麼?)fiber
架構 (React16
版本革命性的變革)