前言
上一篇文章中,我們講解到ReactCompositeComponent[ins]
被初始化後,App[ins]的 render 方法被調用,生成 ReactElement 樹,然後對應的ReactDOMComponent[6]
被返回。下面我們來看看這個ReactDOMComponent[6]
是如何轉化爲 DOM 樹的。
performInitialMount: function (renderedElement, hostParent,
hostContainerInfo, transaction, context) {
...
// 這裏會調用 App 實例的 render 方法,而 render 的返回值是 React.createElement 的嵌套調用。
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
...
// 上回講到這裏
// 返回 ReactDOMComponent[6]
var child = this._instantiateReactComponent(
renderedElement,
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
);
this._renderedComponent = child;
// 今天講這部分
var markup = ReactReconciler.mountComponent(
child,
transaction,
hostParent,
hostContainerInfo,
this._processChildContext(context),
debugID
);
return markup;
},
ReactDOMComponent[6].mountComponent
ReactReconciler.mountComponent 會觸發ReactDOMComponent[6]
的 mountComponent 方法,調用棧如下:
...
|~mountComponentIntoNode() |
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent[T].mountComponent() |
|-ReactCompositeComponent[T].performInitialMount() upper half
|-ReactReconciler.mountComponent() |
|-ReactCompositeComponent[ins].mountComponent() |
|-this.performInitialMount() |
|-this._renderValidatedComponent() |
|-instantiateReactComponent() _|_
(we are here) |
|-ReactDOMComponent[6].mountComponent( |
transaction, // scr: -----> not of interest |
hostParent, // scr: -----> null |
hostContainerInfo,// scr:---------------------> ReactDOMContainerInfo[ins] lower half
context // scr: -----> not of interest |
) |
...
mountComponent: function (
transaction,
hostParent,
hostContainerInfo,
context
) {
...
var mountImage;
if (transaction.useCreateElement) {
var ownerDocument = hostContainerInfo._ownerDocument;
...
// 創建 div 元素
el = ownerDocument.createElement(this._currentElement.type);
...
// 設置 attributes
if (!this._hostParent) {
DOMPropertyOperations.setAttributeForRoot(el);
}
// 設置 properties
this._updateDOMProperties(null, props, transaction);
// 構造 DOM 樹
var lazyTree = DOMLazyTree(el);
// 遍歷子節點並創建 DOM 結點
this._createInitialChildren(transaction, props, context, lazyTree);
mountImage = lazyTree;
}
...
return mountImage;
}
這裏主要做的事情有3部分:
- 創建 DOM 元素
- 設置 attributes 和 properties
- 遍歷子元素並重覆上述過程
這時候的數據結構如下:
流程圖:
_createInitialChildren 遍歷子節點並創建 DOM 結點
下面來看一下 _createInitialChildren 的細節:
_createInitialChildren: function (transaction, props, context, lazyTree) {
// Intentional use of != to avoid catching zero/false.
var innerHTML = props.dangerouslySetInnerHTML;
if (innerHTML != null) {
if (innerHTML.__html != null) {
DOMLazyTree.queueHTML(lazyTree, innerHTML.__html);
}
} else {
// 如果是 string 或者 number,返回 true
var contentToUse =
CONTENT_TYPES[typeof props.children] ? props.children :
null;
var childrenToUse = contentToUse != null ? null : props.children;
// 直接渲染字符串
if (contentToUse != null) {
// Avoid setting textContent when the text is empty. In IE11 setting
// textContent on a text area will cause the placeholder to not
// show within the textarea until it has been focused and blurred again.
// https://github.com/facebook/react/issues/6731#issuecomment-254874553
if (contentToUse !== '') {
DOMLazyTree.queueText(lazyTree, contentToUse);
}
}
// 渲染子節點
else if (childrenToUse != null) {
var mountImages = this.mountChildren(
childrenToUse,
transaction,
context
);
for (var i = 0; i < mountImages.length; i++) {
DOMLazyTree.queueChild(lazyTree, mountImages[i]);
}
}
}
},
這部分代碼十分好懂,就 3 條分支:
- 設置了 dangerouslySetInnerHTML 屬性,直接渲染 HTML
- 子節點類型爲 string 或 number,渲染字符
- 其它情況就需要將 ReactElement 轉換成 ReactDOMComponent 或 ReactCompositeComponent 作進一步的渲染。
DOMLazyTree 的 queueText 和 queueChild 真正有效的都各只有一行代碼:
function queueText(tree, text) {
if (enableLazy) { // scr: NO, I mean, false
...
} else {
setTextContent(tree.node, text);
}
}
var setTextContent = function (node, text) {
if (text) {
var firstChild = node.firstChild;
if (firstChild && firstChild === node.lastChild && firstChild.nodeType === 3) { // scr: false
...
}
}
node.textContent = text; // scr: the only effective line
};
function queueChild(parentTree, childTree) {
if (enableLazy) { // scr: again, false
...
} else {
parentTree.node.appendChild(childTree.node);
}
}
mountChildren 的調用棧如下:
ReactDOMComponent[6].mountComponent() <-------------------------|
(we are here) |
|-this._createInitialChildren() |
?{1} |
|-DOMLazyTree.queueText() |
?{2} |
|-this.mountChildren() // scr: ---------------> 1)(a) |
|-this._reconcilerInstantiateChildren() |
|-ReactChildReconciler.instantiateChildren() |
|-traverseAllChildren() |
|-traverseAllChildrenImpl() <------|inner |
|↻traverseAllChildrenImpl() ------|recursion |
|-instantiateChild() |
|-instantiateReactComponent() |
|↻ReactDOMComponent.mountComponent() // scr: -> 1)(b)---|
|↻DOMLazyTree.queueChild() // scr: ---------------> 2)
這中間的函數調用邏輯很清晰,最終會走到 traverseAllChildrenImpl 這裏:
function traverseAllChildrenImpl(
children,
nameSoFar,
callback,
traverseContext
) {
var type = typeof children;
if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
children = null;
}
if (children === null ||
type === 'string' ||
type === 'number' ||
// The following is inlined from ReactElement. This means we can optimize
// some checks. React Fiber also inlines this logic for similar purposes.
(type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE)) {
callback(
traverseContext,
children,
// If it's the only child, treat the name as if it was wrapped in an array
// so that it's consistent if the number of children grows.
nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) :
nameSoFar
);
return 1;
}
var child;
var nextName;
var subtreeCount = 0; // Count of children found in the current subtree.
var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar +
SUBSEPARATOR;
if (Array.isArray(children)) {
for (var i = 0; i < children.length; i++) {
child = children[i];
nextName = nextNamePrefix + getComponentKey(child, i);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext
);
}
} else {
...
}
return subtreeCount;
}
這裏的邏輯很簡單,如果 children 不是數組,則調用回調函數;如果是數組,則繼續調用自身,相當於深度優先遍歷。這裏的回調函數就是 ReactChildReconciler 中的 instantiateChild:
function instantiateChild(childInstances, child, name, selfDebugID) {
...
if (child != null && keyUnique) {
childInstances[name] = instantiateReactComponent(child, true);
}
}
這裏直接調用 instantiateReactComponent,創建ReactDOMComponent
。所有的ReactDOMComponent
的創建順序如下:
ReactDOMComponent[6].mountComponent()
|-this._createInitialChildren()
|-this.mountChildren()
... |↻instantiateReactComponent()[4,5]
|-ReactDOMComponent[5].mountComponent()
|-this._createInitialChildren()
|-node.textContent = text; // scr: [5] done
|-ReactDOMComponent[4].mountComponent()
|-this._createInitialChildren()
|-this.mountChildren()
... |↻instantiateReactComponent()[2,3]
|-ReactDOMComponent[2].mountComponent() // scr: [2] done
|-ReactDOMComponent[3].mountComponent()
|-this._createInitialChildren()
|-node.textContent = text; // scr: [3] done
|↻node[4].appendChild()[2,3] // scr: [4] done
|↻node[6].appendChild()[4,5] // scr: [6] done
完成的流程圖: