本文使用的是react 15.6.1的代碼
ReactDOM.render
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
let {text} = this.props;
return <div id="app">
<span>{text}</span>
</div>
}
}
App.defaultProps = {
text: 'hello react'
};
ReactDOM.render((
<App/>
), document.getElementById('root'));
相信大家在初學(xué)react的時(shí)候都寫(xiě)過(guò)類似上面的代碼盲再,上篇文章中罢防,已經(jīng)介紹了 extends React.Component
做了些什么喉酌,這次,我們就來(lái)看看ReactDOM.render中react去做了些什么,ReactDOM.render實(shí)際調(diào)用的是ReactMount.js中的render
代碼地址:[ReactMount]
// nextElement即ReactElement司澎,
// container 具體容器房揭,將由virtualDOM生成的真實(shí)dom映射的位置
// callback 渲染成功后的回調(diào)
render: function(nextElement, container, callback) {
return ReactMount._renderSubtreeIntoContainer(
null,
nextElement,
container,
callback,
);
進(jìn)一步得知,其主要邏輯位于_renderSubtreeIntoContainer下
代碼地址:[ReactMount]
// ReactDom.render調(diào)用后 第一個(gè)參數(shù)默認(rèn)傳遞null氯葬,因?yàn)樵摲椒ǖ谝粋€(gè)參數(shù)是父組件挡篓,剩下三個(gè)參數(shù)和render函數(shù)一致
_renderSubtreeIntoContainer: function(
parentComponent,
nextElement,
container,
callback,
) {
//判斷callback是否為函數(shù);
ReactUpdateQueue.validateCallback(callback, 'ReactDOM.render');
var nextWrappedElement = React.createElement(TopLevelWrapper, {
child: nextElement,
});
//通過(guò)TopLevelWrapper創(chuàng)建一個(gè)ReactElement節(jié)點(diǎn),并且設(shè)置其this.props.child = render傳入的ReactElement
/** 上文TopLevelWrapper代碼
var topLevelRootCounter = 1;z
var TopLevelWrapper = function() {
this.rootID = topLevelRootCounter++;
};
TopLevelWrapper.prototype.isReactComponent = {};
TopLevelWrapper.prototype.render = function() {
return this.props.child;
};
TopLevelWrapper.isReactTopLevelWrapper = true;
*/
// 可以看出TopLevelWrapper代碼就是一個(gè)簡(jiǎn)單的ReactComponent,類似于 extend React.Component, 并重寫(xiě)了方法render
var nextContext;
// 如果存在父組件官研,即不是頂級(jí)組件的情況下(在ReactDOM.render時(shí)秽澳,parentComponent為null)
if (parentComponent) {
var parentInst = ReactInstanceMap.get(parentComponent);
nextContext = parentInst._processChildContext(parentInst._context);
} else {
nextContext = emptyObject;
}
// 這時(shí)候preComponent = null;
var prevComponent = getTopLevelWrapperInContainer(container);
if (prevComponent) {
//
var prevWrappedElement = prevComponent._currentElement;
var prevElement = prevWrappedElement.props.child;
// diff 簡(jiǎn)單概括就是如果渲染的節(jié)點(diǎn)和原節(jié)點(diǎn)type和key(所以像listview可以通過(guò)設(shè)置key來(lái)進(jìn)行優(yōu)化)都不變的時(shí)候,直接更新就好戏羽,不用在去重新渲染一遍
if (shouldUpdateReactComponent(prevElement, nextElement)) {
var publicInst = prevComponent._renderedComponent.getPublicInstance();
var updatedCallback =
callback &&
function() {
callback.call(publicInst);
};
ReactMount._updateRootComponent(
prevComponent,
nextWrappedElement,
nextContext,
container,
updatedCallback,
);
return publicInst;
} else {
//否則的話卸載掉該容器的組件
ReactMount.unmountComponentAtNode(container);
}
}
// 獲取container的跟元素
var reactRootElement = getReactRootElementInContainer(container);
// 確定container是否被markup担神,即添加了data-reactid,第一次渲染肯定是false
var containerHasReactMarkup =
reactRootElement && !!internalGetID(reactRootElement);
// 目前為false,因?yàn)镽eactDOM.render調(diào)用時(shí)還沒(méi)有實(shí)例化任何組件
var containerHasNonRootReactChild = hasNonRootReactChild(container);
// 目前為false
var shouldReuseMarkup =
containerHasReactMarkup &&
!prevComponent &&
!containerHasNonRootReactChild;
// 關(guān)鍵代碼始花,渲染妄讯,插入都在這里面
var component = ReactMount._renderNewRootComponent(
nextWrappedElement,
container,
shouldReuseMarkup,
nextContext,
)._renderedComponent.getPublicInstance();
if (callback) {
callback.call(component);
}
return component;
}
根據(jù)代碼我們知道,在_renderSubtreeIntoContainer方法的時(shí)候衙荐,他會(huì)優(yōu)先判斷渲染節(jié)點(diǎn)和原節(jié)點(diǎn)的type和key是否一致捞挥,如果一致,直接調(diào)用_updateRootComponent更新忧吟,否則才會(huì)去重新render新的組件,因此在渲染listview等需要大量刷新的組件時(shí)斩披,可以通過(guò)設(shè)置key去優(yōu)化顯示溜族,減少重新渲染
在看看_renderNewRootComponent的代碼
/**
* Render a new component into the DOM. Hooked by hooks!
*
* @param {ReactElement} nextElement 即將渲染的組件
* @param {DOMElement} container 容器元素
* @param {boolean} shouldReuseMarkup 是否需要重新標(biāo)記元素
* @return {ReactComponent} nextComponent 返回一個(gè)ReactComponent
*/
_renderNewRootComponent: function(
nextElement,
container,
shouldReuseMarkup,
context,
) {
//主要和滾動(dòng)條有關(guān),目前不需要太關(guān)心
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
//實(shí)例化React Component,
var componentInstance = instantiateReactComponent(nextElement, false);
/*
上文instantiateReactComponent
function instantiateReactComponent(node, shouldHaveDebugID) {
var instance;
if (node === null || node === false) {
// 如果是空對(duì)象
instance = ReactEmptyComponent.create(instantiateReactComponent);
} else if (typeof node === 'object') {
// 如果是Node,(包括dom節(jié)點(diǎn)以及reactElement)
var element = node;
// 原生對(duì)象
if (typeof element.type === 'string') {
instance = ReactHostComponent.createInternalComponent(element);
} else {
// react組件
instance = new ReactCompositeComponentWrapper(element);
}
//如果元素本來(lái)就是一個(gè)string或者number垦沉,如 <div>111</div>中的111
} else if (typeof node === 'string' || typeof node === 'number') {
//創(chuàng)建一個(gè)
instance = ReactHostComponent.createInstanceForText(node);
}
//這兩個(gè)參數(shù)用于dom 和 art diff算法
instance._mountIndex = 0;
instance._mountImage = null;
return instance;
}
*/
/* 批量更新方法煌抒,具體實(shí)現(xiàn)可以見(jiàn) ReactDefaultBatchingStrategy.js中 batchedUpdate方法,實(shí)際就是執(zhí)行
* batchedMountComponentIntoNode方法厕倍,將后面的參數(shù)傳入batchedMountComponentIntoNode中
*/
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
componentInstance,
container,
shouldReuseMarkup,
context,
);
var wrapperID = componentInstance._instance.rootID;
instancesByReactRootID[wrapperID] = componentInstance;
return componentInstance;
},
batchedMountComponentIntoNode中寡壮,使用了React中大量使用的事務(wù)機(jī)制,調(diào)用了mountComponentIntoNode方法讹弯,事務(wù)機(jī)制后面有空在來(lái)研究况既,我們直接看mountComponentIntoNode
function mountComponentIntoNode(
wrapperInstance,
container,
transaction,
shouldReuseMarkup,
context,
) {
/*調(diào)用剛才ReactComponent instance中mountComponent方法,將React組件解析成對(duì)應(yīng)的html(對(duì)應(yīng)不同ReactComponent instance)mountComponent也是不同的
<div>hello react</div>, 對(duì)應(yīng)的是ReactDOMTextComponent组民,最終解析成的HTML為<div data-reactroot="x.x.x">hello react</div>
*/
var markup = ReactReconciler.mountComponent(
wrapperInstance,
transaction,
null,
ReactDOMContainerInfo(wrapperInstance, container),
context,
0 /* parentDebugID */,
);
wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
/**
* 將解析出來(lái)的html插入到dom中
*/
ReactMount._mountImageIntoNode(
markup,
container,
wrapperInstance,
shouldReuseMarkup,
transaction,
);
}
看看ReactReconciler中的mountComponent代碼
mountComponent: function(
internalInstance,
transaction,
hostParent,
hostContainerInfo,
context,
parentDebugID, // 0 in production and for roots
) {
var markup = internalInstance.mountComponent(
transaction,
hostParent,
hostContainerInfo,
context,
parentDebugID,
);
if (
internalInstance._currentElement &&
internalInstance._currentElement.ref != null
) {
transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
}
return markup;
},
由代碼可以知道棒仍,其實(shí)本質(zhì)也是調(diào)用了ReactComponent實(shí)例的mountComponent,剛才我們說(shuō)到,不同的ReactComponent實(shí)例會(huì)有不同的mountComponent方法
大體有這三種實(shí)例:
- ReactHostComponent.createInternalComponent(element); 原生組件
- new ReactCompositeComponentWrapper(element); 自定義React組件
- ReactHostComponent.createInstanceForText(node); 字符串/數(shù)字
我們看一下最復(fù)雜的自定義React組件的mountComponent方法
mountComponent: function(
transaction,
hostParent,
hostContainerInfo,
context,
) {
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 a
var doConstruct = shouldConstruct(Component);
// 本質(zhì)上就是new 了一個(gè)React.createClass(), 即Component組件臭胜,代碼可以自己看看莫其,比較簡(jiǎn)單
var inst = this._constructComponent(
doConstruct,
publicProps,
publicContext,
updateQueue,
);
var renderedElement;
// 當(dāng)不存在構(gòu)造函數(shù),并且沒(méi)有new出來(lái)的組件實(shí)例是null或者組件實(shí)例沒(méi)有render方法耸三,那么可以認(rèn)為這是一個(gè)無(wú)狀態(tài)組件
if (!doConstruct && (inst == null || inst.render == null)) {
renderedElement = inst;
warnIfInvalidElement(Component, renderedElement);
// new 一個(gè)無(wú)狀態(tài)組件
inst = new StatelessComponent(Component);
this._compositeType = CompositeTypes.StatelessFunctional;
} else {
if (isPureComponent(Component)) {
this._compositeType = CompositeTypes.PureClass;
} else {
this._compositeType = CompositeTypes.ImpureClass;
}
}
// simpler class abstractions, we set them up after the fact.
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);
var initialState = inst.state;
if (initialState === undefined) {
inst.state = initialState = null;
}
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, // 感謝鐘佳鋒同學(xué)的指正乱陡,這里的renderedElement是后面_renderValidatedComponent中調(diào)用內(nèi)部的render方法獲取
hostParent,
hostContainerInfo,
transaction,
context,
);
}
if (inst.componentDidMount) { //生命周期componentDidMount
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
return markup;
},
從上面我們知道自定義React組件真正實(shí)例化(new)的時(shí)候就在mountComponment之中,但是這只是簡(jiǎn)單的實(shí)例化仪壮,返回值html是在performInitialMount中處理的憨颠,在看看performInitialMount中的代碼
performInitialMount: function(
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context,
) {
var inst = this._instance;
var debugID = 0;
if (inst.componentWillMount) {
//該實(shí)體如果有componentWillMount方法,生命周期之一
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
// 獲取子組件睛驳,實(shí)際是調(diào)用React.createClass中的render方法烙心,因?yàn)閞ender方法會(huì)返回一個(gè)ReactElement對(duì)象
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
var nodeType = ReactNodeTypes.getType(renderedElement);
this._renderedNodeType = nodeType;
//再次調(diào)用instantiateReactComponent初始化
var child = this._instantiateReactComponent(
renderedElement,
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
);
this._renderedComponent = child;
//再一次調(diào)用mountComponent
var markup = ReactReconciler.mountComponent(
child,
transaction,
hostParent,
hostContainerInfo,
this._processChildContext(context),
debugID,
);
return markup;
},
在這段代碼中膜廊,renderedElement被_renderValidatedComponent重新進(jìn)行了賦值,根據(jù)后面child這個(gè)變量命名淫茵,我們猜測(cè)爪瓜,這個(gè)方法會(huì)返回子組件,最后匙瘪,又在一次調(diào)用了ReactReconciler.mountComponent方法铆铆,和mountComponentIntoNode方法調(diào)用手段一致,只是第一個(gè)參數(shù)變成了child,簡(jiǎn)單猜測(cè)一下丹喻,這個(gè)時(shí)候在渲染子組件薄货。為了證實(shí)猜測(cè),在來(lái)看看_renderValidatedComponent方法
_renderValidatedComponent: function() {
var renderedElement;
renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
return renderedElement;
},
對(duì)應(yīng)的_renderValidatedComponentWithoutOwnerOrContext方法
/**
* @protected
*/
_renderValidatedComponentWithoutOwnerOrContext: function() {
var inst = this._instance;
var renderedElement;
//回想一下我們React.createClass中render方法里面被babel解析后是什么
//render中{return React.createElement}
//所以他又是一個(gè)ReactElement
renderedElement = inst.render();
return renderedElement;
},
果然碍论,是調(diào)用了render方法谅猾,也就是render中的子組件。隨后組件又調(diào)用了_instantiateReactComponentfan方法初始化最后再一次mountComponent去實(shí)例化子組件鳍悠,形成遞歸去渲染税娜,直到得到最后的markup,同時(shí)不難想象藏研,隨著不斷的遞歸只有下面的2類會(huì)返回正常的mask對(duì)象
- ReactHostComponent.createInternalComponent(element); 原生組件
- ReactHostComponent.createInstanceForText(node); 字符串/數(shù)字
我們?cè)趤?lái)看看createInternalComponent方法敬矩,該方法會(huì)new一個(gè)ReactDOMComponent(指代原生組件),我們來(lái)看看ReactDOMComponent的mountComponent有什么
mountComponent: function(
transaction,
hostParent,
hostContainerInfo,
context,
) {
this._rootNodeID = globalIdCounter++;
this._domID = hostContainerInfo._idCounter++;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo;
var props = this._currentElement.props;
//針對(duì)不同的element蠢挡,進(jìn)行不同的處理弧岳,在構(gòu)造函數(shù)中this._tag=element.tag.toLowCase()
switch (this._tag) {
case 'audio':
case 'form':
case 'iframe':
case 'img':
case 'link':
case 'object':
case 'source':
case 'video':
this._wrapperState = {
listeners: null,
};
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
case 'input':
ReactDOMInput.mountWrapper(this, props, hostParent);
props = ReactDOMInput.getHostProps(this, props);
transaction.getReactMountReady().enqueue(trackInputValue, this);
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
case 'option':
ReactDOMOption.mountWrapper(this, props, hostParent);
props = ReactDOMOption.getHostProps(this, props);
break;
case 'select':
ReactDOMSelect.mountWrapper(this, props, hostParent);
props = ReactDOMSelect.getHostProps(this, props);
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
case 'textarea':
ReactDOMTextarea.mountWrapper(this, props, hostParent);
props = ReactDOMTextarea.getHostProps(this, props);
transaction.getReactMountReady().enqueue(trackInputValue, this);
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
}
assertValidProps(this, props);
// We create tags in the namespace of their parent container, except HTML
// tags get no namespace.
var namespaceURI;
var parentTag;
if (hostParent != null) {
namespaceURI = hostParent._namespaceURI;
parentTag = hostParent._tag;
} else if (hostContainerInfo._tag) {
namespaceURI = hostContainerInfo._namespaceURI;
parentTag = hostContainerInfo._tag;
}
if (
namespaceURI == null ||
(namespaceURI === DOMNamespaces.svg && parentTag === 'foreignobject')
) {
namespaceURI = DOMNamespaces.html;
}
if (namespaceURI === DOMNamespaces.html) {
if (this._tag === 'svg') {
namespaceURI = DOMNamespaces.svg;
} else if (this._tag === 'math') {
namespaceURI = DOMNamespaces.mathml;
}
}
this._namespaceURI = namespaceURI;
var mountImage;
// 調(diào)用render時(shí),useCreateElement為true
if (transaction.useCreateElement) {
var ownerDocument = hostContainerInfo._ownerDocument;
var el;
if (namespaceURI === DOMNamespaces.html) {
if (this._tag === 'script') {
// Create the script via .innerHTML so its "parser-inserted" flag is
// set to true and it does not execute
var div = ownerDocument.createElement('div');
var type = this._currentElement.type;
div.innerHTML = `<${type}></${type}>`;
el = div.removeChild(div.firstChild);
} else if (props.is) {
el = ownerDocument.createElement(this._currentElement.type, props.is);
} else {
// Separate else branch instead of using `props.is || undefined` above becuase of a Firefox bug.
// See discussion in https://github.com/facebook/react/pull/6896
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
// 創(chuàng)建標(biāo)簽
el = ownerDocument.createElement(this._currentElement.type);
}
} else {
el = ownerDocument.createElementNS(
namespaceURI,
this._currentElement.type,
);
}
ReactDOMComponentTree.precacheNode(this, el);
this._flags |= Flags.hasCachedChildNodes;
if (!this._hostParent) {
DOMPropertyOperations.setAttributeForRoot(el);
}
this._updateDOMProperties(null, props, transaction);
var lazyTree = DOMLazyTree(el);
// 準(zhǔn)備實(shí)例化子組件
this._createInitialChildren(transaction, props, context, lazyTree);
mountImage = lazyTree;
} else {
var tagOpen = this._createOpenTagMarkupAndPutListeners(
transaction,
props,
);
var tagContent = this._createContentMarkup(transaction, props, context);
if (!tagContent && omittedCloseTags[this._tag]) {
mountImage = tagOpen + '/>';
} else {
mountImage =
tagOpen + '>' + tagContent + '</' + this._currentElement.type + '>';
}
}
switch (this._tag) {
case 'input':
transaction.getReactMountReady().enqueue(inputPostMount, this);
if (props.autoFocus) {
transaction
.getReactMountReady()
.enqueue(AutoFocusUtils.focusDOMComponent, this);
}
break;
case 'textarea':
transaction.getReactMountReady().enqueue(textareaPostMount, this);
if (props.autoFocus) {
transaction
.getReactMountReady()
.enqueue(AutoFocusUtils.focusDOMComponent, this);
}
break;
case 'select':
if (props.autoFocus) {
transaction
.getReactMountReady()
.enqueue(AutoFocusUtils.focusDOMComponent, this);
}
break;
case 'button':
if (props.autoFocus) {
transaction
.getReactMountReady()
.enqueue(AutoFocusUtils.focusDOMComponent, this);
}
break;
case 'option':
transaction.getReactMountReady().enqueue(optionPostMount, this);
break;
}
return mountImage;
}
由代碼可以知道react通過(guò)type對(duì)input业踏,select禽炬,script等特殊元素進(jìn)行單獨(dú)的處理,最后調(diào)用createElement來(lái)生成一個(gè)普通的元素堡称,同時(shí)調(diào)用了this._createInitialChildren(transaction, props, context, lazyTree)方法去實(shí)例化其中的子組件瞎抛,同原生組件一下,形成遞歸返回到tree中却紧,最后mountImage = lazyTree;拿到生成的節(jié)點(diǎn)桐臊,這里就不在看原生組件_createInitialChildren的實(shí)現(xiàn)了
回到開(kāi)mountCompouentIntoNode的代碼,最后調(diào)用了ReactMount._mountImageIntoNode
方法晓殊,這個(gè)方法就是真真的把節(jié)點(diǎn)插入帶容器中去了断凶,看一下實(shí)現(xiàn);
_mountImageIntoNode: function(
markup,
container,
instance,
shouldReuseMarkup,
transaction,
) {
//暫時(shí)不用關(guān)心
if (shouldReuseMarkup) {
......
}
//render的時(shí)候該值為true
if (transaction.useCreateElement) {
//移除container中的節(jié)點(diǎn)
while (container.lastChild) {
container.removeChild(container.lastChild);
}
DOMLazyTree.insertTreeBefore(container, markup, null);
} else {
setInnerHTML(container, markup);
ReactDOMComponentTree.precacheNode(instance, container.firstChild);
}
},
最后實(shí)際上是調(diào)用DOMLazyTree.insertTreeBefore完成節(jié)點(diǎn)插入,這一段代碼很簡(jiǎn)單巫俺,就不在單獨(dú)分析了
總結(jié)
終于走完了React整個(gè)渲染流程认烁,我們可以發(fā)現(xiàn)React抽象程度非常的高,源碼讀起來(lái)特別的復(fù)雜,這里也只是簡(jiǎn)單的閱讀整個(gè)流程却嗡,細(xì)節(jié)太多舶沛,沒(méi)有深究