react: v15.0.0html
本文讲 组件如何编译 以及 ReactDOM.render 的渲染过程。node
babel 将 React JSX 编译成 JavaScript.react
在 babel 官网写一段 JSX 代码编译结果如图:babel
每一个标签的建立都调用了 React.createElement.数据结构
贯穿源码,常见的两种数据结构,有助于快速阅读源码。app
结构以下:dom
{
$$typeof // ReactElement标识符
type // 组件
key
ref
props // 组件属性和children
}
复制代码
是 React.createElement 的返回值。函数
ReactComponent 这个名字有点奇怪。ui
结构以下:this
{
_currentElement // ReactElement
...
// 原型链上的方法
mountComponent, // 组件初次加载调用
updateComponent, // 组件更新调用
unmountComponent, // 组件卸载调用
}
复制代码
是 ReactCompositeComponent 的 instance 类型。其他三种构造函数 ReactDOMComponent、ReactDOMTextComponent、ReactEmptyComponent 的实例结构与其类似。
React.createElement 实际执行的是 ReactElement.createElement(目录:src/isomorphic/classic/element/ReactElement.js
)。
ReactElement.createElement 接收三个参数, 返回 ReactElement 结构。
重点关注 type 和 props。
而后看 ReactElement 方法,只是作了赋值动做。
综上,咱们写的代码编译后是这样的:
class C extends React.Component {
render() {
return {
type: "div",
props: {
children: this.props.value,
},
};
}
}
class App extends React.Component {
render() {
return {
type: "div",
props: {
children: [
{
type: "span",
props: {
children: "aaapppppp",
},
},
"123",
{
type: C,
props: {
value: "ccc",
},
},
]
},
};
}
}
ReactDOM.render(
{
type: App,
props: {},
},
document.getElementById("root")
);
复制代码
先来看下 ReactDOM.render 源码的执行过程
在 _renderNewRootComponent 方法中,调用了 instantiateReactComponent(目录:src/renderers/shared/reconciler/instantiateReactComponent.js
),生成了的实例结构相似于 ReactComponent。
instantiateReactComponent 的参数是 node,node 的其中一种格式就是 ReactElement。
根据 node & node.type 的类型,会执行不一样的方法生成实例
简化以下
var instantiateReactComponent = function (node) {
if (node === null || node === false) {
return new ReactEmptyComponent(node);
} else if (typeof node === 'object') {
if (node.type === 'string') {
return new ReactDOMComponent(node);
} else {
return new ReactCompositeComponent(node);
}
} else if (typeof node === 'string' || typeof node === 'number') {
return new ReactDOMTextComponent(node);
}
}
复制代码
经过四种方式实例化后的对象基本类似
var instance = {
_currentElement: node,
_rootNodeID: null,
...
}
instance.__proto__ = {
mountComponent,
updateComponent,
unmountComponent,
}
复制代码
四种 mountComponent 简化以下
源码目录:src/renderers/shared/reconciler/ReactCompositeComponent.js
。
mountComponent: function () {
// 建立当前组件的实例
this._instance = new this._currentElement.type();
// 调用组件的 render 方法,获得组件的 renderedElement
renderedElement = this._instance.render();
// 调用 instantiateReactComponent, 获得 renderedElement 的实例化 ReactComponent
this._renderedComponent = instantiateReactComponent(renderedElement);
// 调用 ReactComponent.mountComponent
return this._renderedComponent.mountComponent();
}
复制代码
源码目录:src/renderers/dom/shared/ReactDOMComponent.js
。
react 源码中,插入 container 前使用 ownerDocument、DOMLazyTree 建立和存放节点,此处为了方便理解,使用 document.createElement 模拟。
mountComponent: function () {
var { type, props } = this._currentElement;
// 建立dom 源码中使用 ownerDocument
var element = document.createElement(type);
// 递归children (源码中使用 DOMLazyTree 存放 并返回)
if (props.children) {
var childrenMarkups = props.children.map(function (node) {
var instance = instantiateReactComponent(node);
return instance.mountComponent();
})
element.appendChild(childrenMarkups)
}
return element;
}
复制代码
源码目录:src/renderers/dom/shared/ReactDOMTextComponent.js
。
mountComponent: function () {
return this._currentElement;
}
复制代码
源码目录:src/renderers/shared/reconciler/ReactEmptyComponent.js
。
mountComponent: function () {
return null;
}
复制代码
简化以下:
ReactDOM.render = function (nextElement, container) {
// 添加壳子
var nextWrappedElement = ReactElement(
TopLevelWrapper,
null,
null,
null,
null,
null,
nextElement
);
// 实例化 ReactElement
var componentInstance = instantiateReactComponent(nextElement);
// 递归生成html
var markup = componentInstance.mountComponent;
// 插入真实dom
container.innerHTML = markup;
}
复制代码