React 初次渲染的时候无非会经历如下四个步骤:
ReactElement 元素的创建,以及它如何表达嵌套在上个章节中也已阐述。React 的难点就在于如何进行数据处理,并且保持高效,我们暂且忽略它。秉持由浅入深的原则进行学习。
想要从 ReactElement 元素的层层嵌套结构中解析出 DOM,能直接想到的办法就是使用递归。在遇到每一层的时候根据元素类型创建不同的 DOM 元素,最后将这一个庞大的 DOM 元素树插入到真实的 DOM 中。
这些功能,都集中在:
packages/react-dom/src/client/ReactDOMComponent.js
createElement
/**
* 创建元素
* @param type
* @param props
* @param rootContainerElement
* @param parentNamespace
* @returns {Element}
*/
export function createElement(
type: string,
props: Object,
rootContainerElement: Element | Document,
parentNamespace: string,
): Element {
let isCustomComponentTag;
// 我们在它们的父容器的命名空间中创建标记,除了 HTML 标签没有命名空间。
// getOwnerDocumentFromRootContainer 方法用来获取 document
const ownerDocument: Document = getOwnerDocumentFromRootContainer(
rootContainerElement,
);
let domElement: Element;
let namespaceURI = parentNamespace;
if (namespaceURI === HTML_NAMESPACE) {
namespaceURI = getIntrinsicNamespace(type);
}
if (namespaceURI === HTML_NAMESPACE) {
if (type === 'script') {
// 通过 .innerHTML 创建脚本,这样它的 “parser-inserted” 标志就被设置为true,而且不会执行
const div = ownerDocument.createElement('div');
div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
// 这将保证生成一个脚本元素。
const firstChild = ((div.firstChild: any): HTMLScriptElement);
domElement = div.removeChild(firstChild);
} else if (typeof props.is === 'string') {
// $FlowIssue `createElement` 应该为 Web Components 更新
domElement = ownerDocument.createElement(type, {
is: props.is});
} else {
// 因为 Firefox bug,分离 else 分支,而不是使用 `props.is || undefined`
// 参见 https://github.com/facebook/react/pull/6896
// 和 https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
domElement = ownerDocument.createElement(type);
// 通常在 `setInitialDOMProperties` 中分配属性,
// 但是 `select` 上的 `multiple` 和 `size` 属性需要在 `option` 被插入之前添加。
// 这可以防止:
// 一个错误,其中 `select` 不能滚动到正确的选项,因为单一的 `select` 元素自动选择第一项 #13222
// 一个 bug,其中 `select` 将第一个项目设置为 selected,而忽略 `size` 属性 #14239
// 参见 https://github.com/facebook/react/issues/13222
// 和 https://github.com/facebook/react/issues/14239
if (type === 'select') {
const node = ((domElement: any): HTMLSelectElement);
if (props.multiple) {
node.multiple = true;
} else if (props.size) {
// 设置大于 1 的 size 会使 select 的行为类似于 `multiple=true`,其中可能没有选择任何选项。
// select 只有在“单一选择模式”下才需要这样做。
node.size = props.size;
}
}
}
} else {
domElement = ownerDocument.createElementNS(namespaceURI, type);
}
return domElement;
}复制代码
createElement
方法会根据不同的类型创建不同的 DOM 元素。值得指出的是,它不仅支持原生的 html 标签,还支持 WebComponent。
createTextNode
export function createTextNode(
text: string,
rootContainerElement: Element | Document,
): Text {
return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(
text,
);
}复制代码
createTextNode
方法创建了一个文本节点
setInitialProperties
export function setInitialProperties(
domElement: Element,
tag: string,
rawProps: Object,
rootContainerElement: Element | Document,
): void {
const isCustomComponentTag = isCustomComponent(tag, rawProps);
// TODO: Make sure that we check isMounted before firing any of these events.
let props: Object;
switch (tag) {
case 'iframe':
case 'object':
trapBubbledEvent(TOP_LOAD, domElement);
props = rawProps;
break;
case 'video':
case 'audio':
// Create listener for each media event
for (let i = 0; i < mediaEventTypes.length; i++) {
trapBubbledEvent(mediaEventTypes[i], domElement);
}
props = rawProps;
break;
case 'source':
trapBubbledEvent(TOP_ERROR, domElement);
props = rawProps;
break;
case 'img':
case 'image':
case 'link':
trapBubbledEvent(TOP_ERROR, domElement);
trapBubbledEvent(TOP_LOAD, domElement);
props = rawProps;
break;
case 'form':
trapBubbledEvent(TOP_RESET, domElement);
trapBubbledEvent(