上回說到了
ReactMount._renderNewRootComponent中調(diào)用了ReactUpdates.batchedUpdates方法,實質(zhì)上調(diào)用的是ReactDefaultBatchingStrategy.batchedUpdates,然后就講了一下Transaction。那么現(xiàn)在回到流程上來,繼續(xù)往下走,現(xiàn)在Transaction也了解了,那么剩下的就好辦了。
回到流程來,我們來看一下ReactDefaultBatchingStrategy.batchedUpdates,首先先說一下參數(shù)
callback: batchedMountComponentIntoNode // 是在ReactMount文件里
a: componentInstance // 執(zhí)行instantiateComponent得到的結(jié)果,那么當前得到的實例是ReactCompositeComponent
b: container
c: shouleReuseMarkup // false
d: context // {}
e: null
// ReactDefaultBatchingStrategy.js
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
}
首先拿到當前的isBatchingUpdates,那么肯定是 false 了啊
然后設(shè)置為 true
那么判斷isBatchingUpdates 賦值的時候是 false,所以執(zhí)行transaction.perform,那么這邊的transaction指的是ReactDefaultBatchingStrategyTransaction
// ReactDefaultBatchingStrategy.js
var transaction = new ReactDefaultBatchingStrategyTransaction();
那么這邊執(zhí)行perform,根據(jù)上一篇博客講的,會首先執(zhí)行所有wrapper的initialize方法。
perform: function (method, scope, a, b, c, d, e, f) {
!!this.isInTransaction() ? /**/
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;
}
但是ReactDefaultBatchingStrategyTransaction包裹的wrapper都是空函數(shù)。
那就到了這一步ret = method.call(scope, a, b, c, d, e, f);開始執(zhí)行我們傳遞的這個方法。也就是執(zhí)行ReactMout里的batchedMountComponentIntoNode
// ReactMount.js
function batchedMountComponentIntoNode(componentInstance, container, shouldReuseMarkup, context) {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
!shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement);
transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);
ReactUpdates.ReactReconcileTransaction.release(transaction);
}
首先從ReactUpdates.ReactReconcileTransaction.getPooled(true)拿到池子里的一個ReactReconcileTransaction實例。ReactUpdates和getPooled在之前的博客已經(jīng)做過聲明了,需要的同學可以自行查看。
然后執(zhí)行perform方法,這邊執(zhí)行的是ReactReconcileTransaction.perform。
最后釋放這個transaction
那么關(guān)鍵的地方在于這邊又執(zhí)行了一個Transction(ReactReconcileTransaction)。
一樣的我們先記錄一下傳遞的參數(shù)。
callback: mountComponentIntoNode // ReactMount.js里的
scope: null
a: componentInstance
b: container
c: transaction // ReactReconcileTransaction
d: shouldReuseMarkup // false
e: context // {}
那么先執(zhí)行所有的initialize
// ReactReconcileTransaction.js
var SELECTION_RESTORATION = {
/**
* @return {Selection} Selection information.
*/
initialize: ReactInputSelection.getSelectionInformation,
/**/
};
var EVENT_SUPPRESSION = {
initialize: function () {
var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
ReactBrowserEventEmitter.setEnabled(false);
return currentlyEnabled;
},
}
var ON_DOM_READY_QUEUEING = {
initialize: function () {
this.reactMountReady.reset();
}
};
第三個wrapper的初始化執(zhí)行this.reactMountReady.reset()實質(zhì)上就是執(zhí)行callbackQueue.reset,這個函數(shù)很簡單就是將callbackQueue的_callbacks _contexts 都清空設(shè)為null
CallbackQueue.prototype.reset = function reset() {
this._callbacks = null;
this._contexts = null;
};
之后就是執(zhí)行包裝的這個函數(shù)mountComponentIntoNode
// ReactMount.js
function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReuseMarkup, context) {
var markerName;
// false
if (ReactFeatureFlags.logTopLevelRenders) {
var wrappedElement = wrapperInstance._currentElement.props.child;
var type = wrappedElement.type;
markerName = 'React mount: ' + (typeof type === 'string' ? type : type.displayName || type.name);
console.time(markerName);
}
var markup = ReactReconciler.mountComponent(wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0 /* parentDebugID */
);
if (markerName) {
console.timeEnd(markerName);
}
wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
ReactMount._mountImageIntoNode(markup, container, wrapperInstance, shouldReuseMarkup, transaction);
}
判斷ReactFeatureFlags.logTopLevelRenders,當前值為false,接下來執(zhí)行ReactReconciler.mountComponent,這個方法也是一個重點。拿到markup后,會執(zhí)行ReactMount._mountImageIntoNode,這個函數(shù)開始將dom掛載在container上。從這就開始有點掛載的樣子了。
這邊可以看到參數(shù)中調(diào)用了一個ReactDOMContainerInfo(wrapperInstance, container),這個函數(shù)主要的作用就是封裝了一下節(jié)點的信息,例如節(jié)點類型,節(jié)點的namespaceURI之類的。那么在函數(shù)調(diào)用時,首先是先調(diào)用這個函數(shù)拿到他的返回值之后作為形參然后調(diào)用原函數(shù)。
// ReactDOMContainerInfo.js
function ReactDOMContainerInfo(topLevelWrapper, node) {
var info = {
_topLevelWrapper: topLevelWrapper,
_idCounter: 1,
_ownerDocument: node ? node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument : null,
_node: node,
_tag: node ? node.nodeName.toLowerCase() : null,
_namespaceURI: node ? node.namespaceURI : null
};
if (process.env.NODE_ENV !== 'production') {
/**/
}
return info;
}
上面這個返回的info就是封裝的節(jié)點信息對象。
先記錄一下參數(shù)
internalInstance:wrapperInstance,這邊的wrapperInstance還是上一個函數(shù)的形參,所以需要用流程圖好好的記錄一下參數(shù)的傳遞。
transaction: ReconcileTransaction
hostParent: null
hostContainerInfo: ReactDOMContainerInfo(wrapperInstance, container)
context: {}
parentDebugId: 0
// ReactReconcile.js
mountComponent: function (internalInstance, transaction, hostParent, hostContainerInfo, context, parentDebugID) // 0 in production and for roots
{
if (process.env.NODE_ENV !== 'production') {
/**/
}
var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID);
if (internalInstance._currentElement && internalInstance._currentElement.ref != null) {
transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
}
if (process.env.NODE_ENV !== 'production') {
/**/
}
return markup;
},
內(nèi)部首先執(zhí)行實例的mountComponent,當前傳入的實例是wrapperInstance,那么這個實例是ReactCompositeComponentWrapper的一個實例。所以這邊轉(zhuǎn)到ReactCompositeComponent.js文件
ReactCompositeComponent
在講解ReactCompositeComponent.mountComponent之前,先看一下React15中的生命周期。React16的生命周期和15是不一樣的話,廢除了幾個換成了幾個新的,這邊就不涉及了。
那么這張圖寫的就很清楚了
在掛載階段
- 根組件的
constructor - 根組件的
componentWillMount - 根組件的
render- 子組件的
constructor - 子組件的
componentWillMount - 子組件的
render - 子組件的
componentDidMount
- 子組件的
- 根組件的
componentDidMount
更新階段 同 掛在階段
卸載階段
- 根組件的
componentWillUnMount - 子組件的
componentWillUnMount - 子組件銷毀
- 根組件銷毀
那么按照我們的例子就是
-
TopLevelWrapper的constructor -
TopLevelWrapper的componentWillMount -
TopLevelWrapper的render-
App的constructor -
App的componentWillMount -
App的render -
App的componentDidMount
-
-
TopLevelWrapper的componentDidMount
mountComponent
// ReactCompositeComponent.js
mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
var _this = this;
this._context = context;
this._mountOrder = nextMountID++;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo;
var publicProps = this._currentElement.props;
var publicContext = this._processContext(context);
var Component = this._currentElement.type;
var updateQueue = transaction.getUpdateQueue();
// Initialize the public class
var doConstruct = shouldConstruct(Component);
var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
var renderedElement;
// Support functional components
if (!doConstruct && (inst == null || inst.render == null)) {
renderedElement = inst;
warnIfInvalidElement(Component, renderedElement);
!(inst === null || inst === false || React.isValidElement(inst)) ? process.env.NODE_ENV !== 'production' ? /**/
inst = new StatelessComponent(Component);
this._compositeType = CompositeTypes.StatelessFunctional;
} else {
if (isPureComponent(Component)) {
this._compositeType = CompositeTypes.PureClass;
} else {
this._compositeType = CompositeTypes.ImpureClass;
}
}
if (process.env.NODE_ENV !== 'production') {
// This will throw later in _renderValidatedComponent, but add an early
// warning now to help debugging
if (inst.render == null) {
process.env.NODE_ENV !== 'production' ? /**/
}
var propsMutated = inst.props !== publicProps;
var componentName = Component.displayName || Component.name || 'Component';
process.env.NODE_ENV !== 'production' ? /**/
}
// These should be set up in the constructor, but as a convenience for
// simpler class abstractions, we set them up after the fact.
// 這些應(yīng)該在構(gòu)造函數(shù)中設(shè)置,但是為了方便更簡單的類抽象,我們在事后才設(shè)置它們。
inst.props = publicProps;
inst.context = publicContext;
inst.refs = emptyObject;
inst.updater = updateQueue;
this._instance = inst;
// Store a reference from the instance back to the internal representation
ReactInstanceMap.set(inst, this);
if (process.env.NODE_ENV !== 'production') {
/**/
}
var initialState = inst.state;
if (initialState === undefined) {
inst.state = initialState = null;
}
!(typeof initialState === 'object' && !Array.isArray(initialState)) ? /**/
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
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 (process.env.NODE_ENV !== 'production') {
transaction.getReactMountReady().enqueue(function () {
measureLifeCyclePerf(function () {
return inst.componentDidMount();
}, _this._debugID, 'componentDidMount');
});
} else {
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
}
return markup;
},
代碼有點長,那么來一塊一塊的看。
那么首先是給當前實例添加一些屬性
this._context = context;
this._mountOrder = nextMountID++;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo;
這邊呢有一個nextMountID變量,這是外部閉包的一個變量,用于記錄著掛載的順序的。
/**
* An incrementing ID assigned to each when it is mounted. This is
* used to enforce the order in which `ReactUpdates` updates dirty components.
* 這用于強制執(zhí)行“ReactUpdates”更新臟組件的順序。
* @private
*/
var nextMountID = 1;
再往下
var updateQueue = transaction.getUpdateQueue();
這邊獲取了當前事務(wù)的ReactUpdateQueue實例。
// Initialize the public class
var Component = this._currentElement.type;
var doConstruct = shouldConstruct(Component); // true
這邊調(diào)用shouldConstruct函數(shù)對this._currentElement.type做一個判斷,判斷原型鏈中是否是isReactComponent屬性。而當前的_currentElement是一開始的nextWrapperElement他是對TopLevelWrapper執(zhí)行ReactElement方法得到的ReactElement。那么當前的type就是這邊的TopLevelWrapper。那么找ReactMount.js發(fā)現(xiàn),TopLevelWrapper的原型鏈上是有isReactComponent屬性的。
shouldConstruct
// ReactCompositeComponent.js
function shouldConstruct(Component) {
return !!(Component.prototype && Component.prototype.isReactComponent);
}
// ReactMount.js
var TopLevelWrapper = function () {
this.rootID = topLevelRootCounter++;
};
TopLevelWrapper.prototype.isReactComponent = {};
接下來執(zhí)行this._constructComponent方法。傳遞參數(shù)為
doConstruct: true
publicProps: this._currentElement.props // 當前為nextWrapperElement的props,存儲的是type為App構(gòu)造函數(shù)的ReactElement
publicContext: this._processContext(context) // {}
updateQueue: transaction.getUpdateQueue() // ReactUpdateQueue實例
_constructComponent
_constructComponent: function (doConstruct, publicProps, publicContext, updateQueue) {
if (process.env.NODE_ENV !== 'production' && !doConstruct) {
/**/
} else {
return this._constructComponentWithoutOwner(doConstruct, publicProps, publicContext, updateQueue);
}
},
本質(zhì)上是執(zhí)行的this._constructComponentWithoutOwner,參數(shù)還是原封不動的傳遞過去
_constructComponentWithoutOwner
_constructComponentWithoutOwner: function (doConstruct, publicProps, publicContext, updateQueue) {
var Component = this._currentElement.type;
if (doConstruct) {
if (process.env.NODE_ENV !== 'production') {
/**/
} else {
return new Component(publicProps, publicContext, updateQueue);
}
}
// This can still be an instance in case of factory components
// but we'll count this as time spent rendering as the more common case.
if (process.env.NODE_ENV !== 'production') {
/**/
} else {
return Component(publicProps, publicContext, updateQueue);
}
},
如果這邊的doConstruct是true的話,那么就會返回一個this._currentElement.type的實例,否則的話則會調(diào)用this._currentElement.type返回其返回值。這邊籠統(tǒng)的說就是要實例化一個this._currentElement.type存儲的值。
那么當前doConstruct是為true的,也就是說會返回一個TopLevelWrapper實例。
那么回到mountComponent方法中繼續(xù)往下走
var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
這邊的inst現(xiàn)在就是TopLevelWrapper實例了。繼續(xù)往下走
// Support functional components
if (!doConstruct && (inst == null || inst.render == null)) {
renderedElement = inst;
warnIfInvalidElement(Component, renderedElement);
!(inst === null || inst === false || React.isValidElement(inst)) ? process.env.NODE_ENV !== 'production' ? /**/
inst = new StatelessComponent(Component);
this._compositeType = CompositeTypes.StatelessFunctional;
} else {
if (isPureComponent(Component)) {
this._compositeType = CompositeTypes.PureClass; // 1
} else {
this._compositeType = CompositeTypes.ImpureClass; // 0
}
}
第一個if根據(jù)注釋猜測是函數(shù)式組件才會調(diào)用。那么當前的情況是進入到了else內(nèi)部。主要操作在于給當前實例添加_compositeType對象。值是根據(jù)isPureComponent函數(shù)來決定的。
這邊的CompositeTypes是外部閉包的一個對象,其實就是一個枚舉。
var CompositeTypes = {
ImpureClass: 0, // 非 PureClass
PureClass: 1, // PureClass
StatelessFunctional: 2 // 無狀態(tài)的函數(shù)式組件
};
那么總的來說就是如果我們這個Component是PureClass的話,_compositeType對應(yīng)CompositeTypes.ImpureClass,否則的話就是CompositeTypes.PureClass。在第一篇博客講解React的全局API時,對于PureComponent是有一個特殊的標志的isPureReactComponent。
// ReactCompositeComponent.js
function isPureComponent(Component) {
return !!(Component.prototype && Component.prototype.isPureReactComponent);
}
// react/lib/ReactBaseClass.js
function ReactPureComponent(props, context, updater) {
// Duplicated from ReactComponent.
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
ReactPureComponent.prototype.isPureReactComponent = true;
那么接著往下
// These should be set up in the constructor, but as a convenience for
// simpler class abstractions, we set them up after the fact.
// 這些應(yīng)該在構(gòu)造函數(shù)中設(shè)置,但是為了方便更簡單的類抽象,我們在事后才設(shè)置它們。
inst.props = publicProps;
inst.context = publicContext;
inst.refs = emptyObject;
inst.updater = updateQueue;
給inst附加屬性,官方注釋的解釋是這些屬性本應(yīng)在構(gòu)造函數(shù)中設(shè)置但是為了更簡單的類抽象,才在這邊進行附加。
this._instance = inst;
給當前實例附加_instance屬性,值為inst
// Store a reference from the instance back to the internal representation
ReactInstanceMap.set(inst, this);
將當前的inst存儲起來。ReactInstanceMap對象做的事情就是一件事,維護來自面向公共的有狀態(tài)實例(鍵)和內(nèi)部表示(值)的映射。這允許公共方法接受面向用戶的實例作為參數(shù),并將它們映射回內(nèi)部方法。
說白了給key值附加一個屬性_reactInternalInstance,值為傳入的value。
那么這邊就是給inst附加一個_reactInternalInstance屬性,值為this也就是當前ReactCompositeComponent實例。
var initialState = inst.state;
if (initialState === undefined) {
inst.state = initialState = null;
}
獲取初始的state,不存在的話初始化為null
走這開始就可以把生命周期給連上了。這一步對應(yīng)著我們在構(gòu)造函數(shù)里寫的this.state = {},例子如下
class App extends React.Component {
constructor(props) {
super(props);
// 對應(yīng)著這里
this.state = {
name: 'Hello World'
}
}
/**/
}
但是我們當前的inst是TopLevelWrapper顯然沒有這么個東西。
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
附加幾個屬性,這幾個屬性用在更新的時候。
var markup;
if (inst.unstable_handleError) {
markup = this.performInitialMountWithErrorHandling(renderedElement, hostParent, hostContainerInfo, transaction, context);
} else {
markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
}
這邊對inst.unstable_handleError判斷之后執(zhí)行初始化的掛載,那么這邊顯然沒有這個屬性。這個performInitialMount方法就對應(yīng)著我們的componentWillMount生命周期。
performInitialMount
performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) {
var inst = this._instance;
var debugID = 0;
if (process.env.NODE_ENV !== 'production') {
debugID = this._debugID;
}
if (inst.componentWillMount) {
if (process.env.NODE_ENV !== 'production') {
/**/
} 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);
}
}
// If not a stateless component, we now render
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
var nodeType = ReactNodeTypes.getType(renderedElement);
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
);
this._renderedComponent = child;
var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID);
if (process.env.NODE_ENV !== 'production') {
/**/
}
return markup;
},
那么上面也提到了這個函數(shù)相當于componentWillMount,執(zhí)行初始化的掛載。
if (inst.componentWillMount) {
if (process.env.NODE_ENV !== '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);
}
}
那么這邊判斷componentWillMount是否存在,存在的話就執(zhí)行,在執(zhí)行期間執(zhí)行的setState將會加入到_pendingStateQueue,這邊需要注意的是,在componentWillMount期間執(zhí)行的setState并不會引發(fā)重新的render。
執(zhí)行完之后判斷_pendingStateQueue是否存在,也就是判斷在執(zhí)行期間是否調(diào)用了setState,如果有的話會將這個隊列中的state進行比較,得到一個最終的state。
_processPendingState
_processPendingState: function (props, context) {
var inst = this._instance;
var queue = this._pendingStateQueue;
var replace = this._pendingReplaceState;
this._pendingReplaceState = false;
this._pendingStateQueue = null;
if (!queue) {
return inst.state;
}
if (replace && queue.length === 1) {
return queue[0];
}
var nextState = _assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
}
return nextState;
}
通過Object.assign一個一個的覆蓋前者得到一個最終的state。得到最終的一個state之后賦值給inst.state,之后就是判斷傳入的這個renderedElement是否是undefined了。
// If not a stateless component, we now render
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
根據(jù)這個英文注釋了解到如果是一個無狀態(tài)組件組件,那么就渲染他,突然發(fā)現(xiàn)我們的TopLevelWrapper就是一個無狀態(tài)的組件,那么當前就是undefined,ok,渲染他。
_renderValidatedComponent
_renderValidatedComponent: function () {
var renderedElement;
if (process.env.NODE_ENV !== 'production' || this._compositeType !== CompositeTypes.StatelessFunctional) {
ReactCurrentOwner.current = this;
try {
renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
} finally {
ReactCurrentOwner.current = null;
}
} else {
renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
}
!(
// TODO: An `isValidNode` function would probably be more appropriate
renderedElement === null || renderedElement === false || React.isValidElement(renderedElement)) ? process.env.NODE_ENV !== 'production' ? /**/
return renderedElement;
},
那么實際上就是執(zhí)行_renderValidatedComponentWithoutOwnerOrContext函數(shù)并將返回值返回出來。
_renderValidatedComponentWithoutOwnerOrContext
_renderValidatedComponentWithoutOwnerOrContext: function () {
var inst = this._instance;
var renderedElement;
if (process.env.NODE_ENV !== 'production') {
renderedElement = measureLifeCyclePerf(function () {
return inst.render();
}, this._debugID, 'render');
} else {
renderedElement = inst.render();
}
if (process.env.NODE_ENV !== 'production') {
// We allow auto-mocks to proceed as if they're returning null.
if (renderedElement === undefined && inst.render._isMockFunction) {
// This is probably bad practice. Consider warning here and
// deprecating this convenience.
renderedElement = null;
}
}
return renderedElement;
},
這個就簡單了,直接執(zhí)行this._instance.render,那么當前的_instance是TopLevelWrapper實例,他的render方法就是返回他的props.child
// ReactMount.js
TopLevelWrapper.prototype.render = function () {
return this.props.child;
};
那么這邊的child就是我們的React.createElement(App)的結(jié)果。將返回值依次返回那么就回到了performInitialMount函數(shù)。
// ReactCompositeComponent.performInitialMount
// If not a stateless component, we now render
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
也就是回到了這,如果有童鞋打斷點的話,那么可以看
Chrome 里面的Call Stack看整個流程的函數(shù)調(diào)用棧。
那么繼續(xù)往下走
var nodeType = ReactNodeTypes.getType(renderedElement);
this._renderedNodeType = nodeType;
這邊給當前實例附加一個_renderedNodeType屬性,調(diào)用了一個ReactNodeTypes.getType()方法,
// ReactNodeType.js
var ReactNodeTypes = {
HOST: 0,
COMPOSITE: 1,
EMPTY: 2,
getType: function (node) {
if (node === null || node === false) {
return ReactNodeTypes.EMPTY;
} else if (React.isValidElement(node)) {
if (typeof node.type === 'function') {
return ReactNodeTypes.COMPOSITE;
} else {
return ReactNodeTypes.HOST;
}
}
!false ? process.env.NODE_ENV !== 'production' ? /**/
}
};
根據(jù)node型和node.type進行判斷,主要目的就是判斷當前的node是哪一種類型的組件,這個操作在instantiateReactComponent中有類似的操作。繼續(xù)往下
var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY);
調(diào)用_instantiateReactComponent函數(shù),該函數(shù)是在實例化當前實例時進行賦值的也就是instantiateReactComponent.js里面的方法,重要目的就是創(chuàng)建一個ReactComponent實例。
具體的操作就不去看了之前已經(jīng)介紹過,那么我們需要注意的是這邊傳入的renderedElement參數(shù)是我們的類型為App的ReactElement,那么根據(jù)之前的介紹,這個方法會返回一個ReactCompositeComponent的實例。
那么繼續(xù)往下
this._renderedComponent = child;
附加_renderedComponent屬性值為上一步得到的child
繼續(xù)往下
var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID);
執(zhí)行ReactReconcile.mountComponent,那么又回到了ReactCompositeComponent.mountComponent函數(shù),只不過this指向了這個child
那么我們來看一下當前的調(diào)用棧
再來看一下this指向
可以看到this._currentElement是type為App的ReactElement。
那么還是一樣的附加幾個屬性,這邊的_mountOrder就是2了
this._mountOrder = nextMountID++;
再往下經(jīng)過一系列平淡無奇的操作之后到了這
var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
這邊需要注意的是實例化App時,傳入了這個updateQueue,而App的構(gòu)造函數(shù)內(nèi)部調(diào)用了super(props)調(diào)用了一次父類的構(gòu)造函數(shù)也就是React.Component的構(gòu)造函數(shù),
// ReactBaseClasses.js
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;
}
那么這邊只傳遞了props,其余兩個變量都是undefined,那么重點在這
this.updater = updater || ReactNoopUpdateQueue;
這邊的this.updater賦值的是ReactNoopUpdateQueue對象。
那么我們來看一下這個App的實例有哪些屬性。
原型鏈上的幾個方法是通過babel打進去的。
在往下又經(jīng)歷了一系列的操作到了這
inst.props = publicProps;
inst.context = publicContext;
inst.refs = emptyObject;
inst.updater = updateQueue;
重點在于最后一句,對inst.updater重新賦值了,一開始實例化App的時候用的是缺省的ReactNoopUpdateQueue,那么這邊是重新改成ReactUpdateQueue,我估計是防止有人在執(zhí)行super()的時候,傳了不止props一個參數(shù),這邊做一個重新賦值保證updater一定是ReactUpdateQueue.
再次經(jīng)歷一系列的操作之后進入了performInitialMount函數(shù),開始執(zhí)行componentWillMount方法,但是我們的componentWillMount方法里調(diào)用了setState,我們來看一下React對setState的處理。
ReactComponent.prototype.setState = function (partialState, callback) {
!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? /**/
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
調(diào)用this.updater.enqueueSetState方法,那么我們這邊的updater上面也說了是一個ReactUpdateQueue,來看一下這個對象里的方法。
//ReactUpdateQueue.js
enqueueSetState: function (publicInstance, partialState) {
if (process.env.NODE_ENV !== 'production') {
/**/
}
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
},
那么這邊調(diào)用getInternalInstanceReadyForUpdate方法從ReactInstanceMap?中查找對應(yīng)的key值,主要就是查找對應(yīng)的實例是否存在,不存在的話就報錯退出。
之后就是將新的state加入到實例的_pendingStateQueue里面,上文也說了執(zhí)行完componentWillMount之后會檢查_pendingStateQueue得到最新的state之后對實例的state進行賦值。
之后調(diào)用enqueueUpdate方法
// ReactUpdateQueue.js
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
實際上就是執(zhí)行ReactUpdates.enqueueUpdate,ok,我們跳到這里看一下發(fā)生了什么。
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
// 我們代碼的各個部分(比如ReactCompositeComponent的_renderValidatedComponent)
// 假設(shè)render調(diào)用不是嵌套的;驗證一下。(這是由每個頂級更新函數(shù)調(diào)用,如setState、forceUpdate等;
// 頂級組件的創(chuàng)建和銷毀在ReactMount中受到保護。)
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
首先確保依賴注入,之后判斷batchingStrategy.isBatchingUpdates變量,那么當前值還是true的。所以下面就將該組件push進dirtyComponents,并且給組件添加一個_updateBatchNumber值,這個updateBatchNumber是外部閉包的一個變量。
一路return回到了performInitialMount函數(shù),開始檢測_pendingStateQueue,這邊按照我們的示例是有一個的,那么就計算出最新的state,那么可以發(fā)現(xiàn)React是進行批處理的。
之后因為renderedElement是undefined開始執(zhí)行render,就到了_renderValidatedComponent函數(shù),繼而又到了_renderValidatedComponentWithoutOwnerOrContext函數(shù),執(zhí)行實例的render方法,我們來看一下App的render方法
render() {
console.log('render');
return (
<div>
{ this.state.name }
</div>
)
}
render函數(shù)打印一句話然后返回一個DOM節(jié)點。然后一路返回到了performInitialMount方法。
一路平淡無奇到了這
var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY);
那么這會的renderedElement就是我們render返回出來的那個DOM節(jié)點,只不過被babel調(diào)用了React.createElement解析成了一個ReactElement。我們來看一下當前的這個ReactElement
那么這邊調(diào)用_instantiateReactComponent實際上也就是instantiateReactComponent方法,這邊因為node.type是一個字符串,那么會調(diào)用ReactHostComponent.createInternalComponent創(chuàng)建一個ReactHostComponent實例,實際上也就是ReactDOMComponent實例。
拿到了child之后,又開始對child執(zhí)行掛載,這會執(zhí)行的就是ReactDOMComponent.mountComponent。
一系列操作完之后,回到了ReactCompositeComponent.mountComponnet中繼續(xù)往下執(zhí)行。到了這也就是App以下的都掛載完了,開始執(zhí)行App的componentDidMount
if (inst.componentDidMount) {
if (process.env.NODE_ENV !== 'production') {
/**/
} else {
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
}
這邊直接是把componentDidMount方法壓入ReactReconcileTransaction的callbackQueue隊列中。因為這邊transaction.getReactMountReady()是返回的一個callbackQueue。
到此App的componentDidMount之前的都已經(jīng)執(zhí)行完了,回到了ReactReconcile.mountComponent.
mountComponent: function (internalInstance, transaction, hostParent, hostContainerInfo, context, parentDebugID) // 0 in production and for roots
{
if (process.env.NODE_ENV !== 'production') {
/**/
}
// 走這開始調(diào)用實例的掛載
var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID);
if (internalInstance._currentElement && internalInstance._currentElement.ref != null) {
transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
}
if (process.env.NODE_ENV !== 'production') {
if (internalInstance._debugID !== 0) {
ReactInstrumentation.debugTool.onMountComponent(internalInstance._debugID);
}
}
return markup;
}
那么到現(xiàn)在我們可以理會到了ReactReconcile的作用了,他內(nèi)部的mountComponent是所有實例掛載的入口,在ReactReconcile內(nèi)部才會開始調(diào)用實例自己的mountComponent。而Reconcile調(diào)度也就是這個意思。
那么再執(zhí)行完實例自己的mountComponent之后,判斷實例是否具有ref屬性,如果有的話會將一個函數(shù)名為attachRefs,調(diào)用上下文為當前傳入的實例(internalInstance)壓入到事務(wù)的callbackQueue中。那么之所以要將函數(shù)和上下文都一起保存是因為是要確保回調(diào)函數(shù)的內(nèi)部this指向。
那么之后就是一路的return,回到了mountComponentIntoNode,開始執(zhí)行ReactMount._mountImageIntoNode方法
_mountImageIntoNode: function (markup, container, instance, shouldReuseMarkup, transaction) {
/**/
if (shouldReuseMarkup) {
var rootElement = getReactRootElementInContainer(container);
if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) {
ReactDOMComponentTree.precacheNode(instance, rootElement);
return;
} else {
var checksum = rootElement.getAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);
rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);
var rootMarkup = rootElement.outerHTML;
rootElement.setAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME, checksum);
var normalizedMarkup = markup;
if (process.env.NODE_ENV !== 'production') {
/**/
}
var diffIndex = firstDifferenceIndex(normalizedMarkup, rootMarkup);
var difference = ' (client) ' + normalizedMarkup.substring(diffIndex - 20, diffIndex + 20) + '\n (server) ' + rootMarkup.substring(diffIndex - 20, diffIndex + 20);
/**/
}
/**/
if (transaction.useCreateElement) {
// 清空容器子節(jié)點,換行符也算一個childNode,lastChild,firstChild是取的childNodes,不是children,換行符也算一個childNode
while (container.lastChild) {
container.removeChild(container.lastChild);
}
DOMLazyTree.insertTreeBefore(container, markup, null);
} else {
setInnerHTML(container, markup);
ReactDOMComponentTree.precacheNode(instance, container.firstChild);
}
if (process.env.NODE_ENV !== 'production') {
/**/
}
}
首先對shouleReuseMarkup做判斷,當前為false,往下就是判斷transaction.useCreateElement,那么這個useCreateElement是在實例化ReactReconcileTransaction時,傳給構(gòu)造函數(shù)的一個參數(shù),當前為true。ok,進入這個if內(nèi)部
一個while清空container內(nèi)部的所有子節(jié)點
打斷點走到這可以看出root節(jié)點里的子節(jié)點都被清空了,然后開始執(zhí)行DOMLazyTree.insertTreeBefore方法,開始將虛擬DOM插入到真實DOM中。
var insertTreeBefore = createMicrosoftUnsafeLocalFunction(function (parentNode, tree, referenceNode) {
// DocumentFragments aren't actually part of the DOM after insertion so
// appending children won't update the DOM. We need to ensure the fragment
// is properly populated first, breaking out of our lazy approach for just
// this level. Also, some <object> plugins (like Flash Player) will read
// <param> nodes immediately upon insertion into the DOM, so <object>
// must also be populated prior to insertion into the DOM.
// 文檔片段在插入之后實際上不是DOM的一部分,所以附加的子元素不會更新DOM。
// 我們需要確保首先正確填充了片段,從而打破只針對這個級別的惰性方法。
// 此外,一些<object>插件(如Flash Player)在插入到DOM時將立即讀取<param>節(jié)點,因此在插入到DOM之前還必須填充<object>。
if (tree.node.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE || tree.node.nodeType === ELEMENT_NODE_TYPE && tree.node.nodeName.toLowerCase() === 'object' && (tree.node.namespaceURI == null || tree.node.namespaceURI === DOMNamespaces.html)) {
insertTreeChildren(tree);
parentNode.insertBefore(tree.node, referenceNode);
} else {
parentNode.insertBefore(tree.node, referenceNode);
insertTreeChildren(tree);
}
});
對tree.node.nodeType做判斷,當前進入else代碼塊,只看代碼的話會發(fā)現(xiàn)if和else的代碼塊只是順序不一樣。那么接下來就是DOM操作了,將tree.node插入到parent的子節(jié)點中,這邊的referenceNode是null,表明在parentNode的子節(jié)點末尾插。
那么這個時候我們發(fā)現(xiàn)這個時候節(jié)點已經(jīng)插進去了
接下來調(diào)用`insertTreeChildren方法
function insertTreeChildren(tree) {
if (!enableLazy) {
return;
}
var node = tree.node;
var children = tree.children;
if (children.length) {
for (var i = 0; i < children.length; i++) {
insertTreeBefore(node, children[i], null);
}
} else if (tree.html != null) {
setInnerHTML(node, tree.html);
} else if (tree.text != null) {
setTextContent(node, tree.text);
}
}
這邊用到了一個變量enableLazy,這是根據(jù)不同的瀏覽器來判定的。理由在源碼的英文注釋中給出了
In IE (8-11) and Edge, appending nodes with no children is dramatically
faster than appending a full subtree, so we essentially queue up the
.appendChild calls here and apply them so each node is added to its parent
before any children are added.
In other browsers, doing so is slower or neutral compared to the other order
(in Firefox, twice as slow) so we only do this inversion in IE.
大致意思就是在IE瀏覽器和Edge瀏覽器(Microsoft Edge)中,添加沒有子節(jié)點的節(jié)點比直接添加完整的DOM樹要快得多。
在其他瀏覽器中,這樣做比另一種順序(在Firefox中,是兩倍慢)更慢或更中立,所以我們只在IE中做這種倒置。
然后就對瀏覽器做了一個判斷
var enableLazy = typeof document !== 'undefined' && typeof document.documentMode === 'number' || typeof navigator !== 'undefined' && typeof navigator.userAgent === 'string' && /\bEdge\/\d/.test(navigator.userAgent);
如果是IE的話那么enableLazy就是true,那么當前是用的Chrome。所以這個方法就直接return了。這一塊做的就是對IE瀏覽器做不同的插入操作。IE真的是前端的。。。。。
那么到這整個ReactReconcileTransaction.perform里的callback就都執(zhí)行完了,虛擬節(jié)點已經(jīng)轉(zhuǎn)換為真實的DOM節(jié)點完成了掛載,但是我們還沒有執(zhí)行componentDidMount生命周期方法。
之前也看到這個方法是添加到了事務(wù)的callbackQueue隊列中了,事實上,這個生命周期會在事務(wù)關(guān)閉的時候被執(zhí)行,別忘了?事務(wù)在關(guān)閉的時候會執(zhí)行所有的close的。
整個流程已經(jīng)走到了這。下面就開始執(zhí)行closeAll方法執(zhí)行外層包裹的wrapper的close方法了。
// ReactReconcileTransaction.js
var SELECTION_RESTORATION = {
/**/
/**
* @param {Selection} sel Selection information returned from `initialize`.
*/
close: ReactInputSelection.restoreSelection
};
var EVENT_SUPPRESSION = {
/**/
/**
* @param {boolean} previouslyEnabled Enabled status of
* `ReactBrowserEventEmitter` before the reconciliation occurred. `close`
* restores the previous value.
*/
close: function (previouslyEnabled) {
ReactBrowserEventEmitter.setEnabled(previouslyEnabled);
}
};
var ON_DOM_READY_QUEUEING = {
/**/
/**
* After DOM is flushed, invoke all registered `onDOMReady` callbacks.
*/
close: function () {
this.reactMountReady.notifyAll();
}
};
那么前兩個close我們就不關(guān)注了,來看第三個close,執(zhí)行reactMountReady.notifyAll()方法。
那么這個reactMountReady本質(zhì)上是一個callbackQueue,而notifyAll方法就是來執(zhí)行收集到了所有的回調(diào),并通過call方法將回調(diào)函數(shù)的this指向到對應(yīng)的context。
CallbackQueue.prototype.notifyAll = function notifyAll() {
var callbacks = this._callbacks;
var contexts = this._contexts;
var arg = this._arg;
if (callbacks && contexts) {
!(callbacks.length === contexts.length) ? process.env.NODE_ENV !== 'production' ? /**/
this._callbacks = null;
this._contexts = null;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i].call(contexts[i], arg);
}
callbacks.length = 0;
contexts.length = 0;
}
};
那么目前收集到的回調(diào)只有一個componentDidMount,我們的示例中,在這個生命周期函數(shù)中打印了一句話調(diào)用了一次setState,那么根據(jù)上面說的setState會將組件標記為dirtyComponent并將新的state添加進實例的_pendingStateQueue。
調(diào)用完所有收集到的回調(diào)之后就清空_callback, _context兩個數(shù)組。
執(zhí)行完ReactReconcileTransaction所有的close之后,將wrapperInitData清空,在事務(wù)的最后也就是finally代碼快,將事務(wù)正在執(zhí)行事務(wù)的標記變量記為false。
finally {
try {
if (errorThrown) {
// If `method` throws, prefer to show that stack trace over any thrown
// by invoking `closeAll`.
try {
this.closeAll(0);
} catch (err) {}
} else {
// Since `method` didn't throw, we don't want to silence the exception
// here.
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
那么整個調(diào)度事務(wù)到這就都執(zhí)行完了。那么到這整個流程就回到了batchedMountComponentIntoNode方法,接下來就是需要調(diào)用ReactUpdates.ReactReconcileTransaction.release釋放這個調(diào)度事務(wù)。
// ReactMount.js
function batchedMountComponentIntoNode(componentInstance, container, shouldReuseMarkup, context) {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
!shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement);
transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);
ReactUpdates.ReactReconcileTransaction.release(transaction);
}
那么到這整個batchedMountComponentIntoNode方法就算執(zhí)行完了,但是這個方法是ReactDefaultBatchingStrategyTransaction事務(wù)的回調(diào)。根據(jù)事務(wù)的規(guī)矩,執(zhí)行完回調(diào)就需要執(zhí)行close了。
// ReactDefaultBatchingStrategy.js
var RESET_BATCHED_UPDATES = {
/**/
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
那么第一個就不說了,設(shè)置一下isBatchingUpdates,第二個才是關(guān)鍵,調(diào)用了ReactUpdates.flushBatchedUpdates刷新更新隊列。下面的東西就是更新階段的事情了,因為我們在掛載階段的兩個生命周期調(diào)用了setState,當前dirtyComponents中包含兩項,這個flushBatchedUpdates就是要刷新dirtyComponents來完成更新。更新階段的事下回再說。