1. 从 jsx到dom 的过程
class Form extends React.Component {
constructor() {
super();
}
render() {
return (
<form>
<input type="text"/>
</form>
);
}
}
ReactDOM.render( (
<div className="test">
<span onClick={function(){}}>CLICK ME</span>
<Form/>
</div>
), document.getElementById('main'))
1.1 打包时babel 把 jsx 转成 浏览器可以执行的 js ,如下代码, (react17 会使用 JSX 转换器添加 react/jsx-runtime 和 react/jsx-dev-runtime)
React.createElement( 'div', {
className: 'test'
},
React.createElement( 'span',
{ onClick: function(){} },
'CLICK ME'
),
React.createElement(Form, null)
)
1.2 API: React.createElement(component, props, …children) 它生成一个 js 的对象, 俗称的虚拟dom,
(虚拟 dom 的意思是用 js 对象结构模拟出 html 中 dom 结构,批量的增删改查先直接操作 js 对象,最后更新到真正的 dom 树上, 要比操作真实dom要快), 生成对应出来的虚拟 dom
{
type: 'div',
props: {
className: 'xxx',
children: [ {
type: 'span',
props: {
children: [ 'CLICK ME' ]
},
ref:
key:
}, {
type: Form,
props: {
children: []
},
ref:
key:
} ] | Element
}
ref: 'xxx',
key: 'xxx'
}
1.3 有了虚拟 dom,接下来的工作就是把这个虚拟 dom 树真正渲染成一个 dom 树,
React 的做法是针对不同的 type 构造相应的渲染对象,渲染对象提供一个 mountComponent 方法(负责把对应的某个虚拟 dom 的节点生成成具体的 dom node),然后循环迭代整个 vdom tree 生成一个完整的 dom node tree,最终插入容器节点。查看源码你会发现如下代码
// vdom 是第2步生成出来的虚拟 dom 对象
var renderedComponent = instantiateReactComponent( vdom );
// dom node
var markup = renderedComponent.mountComponent();
// 把生成的 dom node 插入到容器 node 里面,真正在页面上显示出来
// 下面是伪代码,React 的 dom 操作封装在 DOMLazyTree 里面
containerNode.appendChild( markup );
1.3.1 instantiateReactComponent 传入的是虚拟 dom 节点,这个方法做的就是根据不同的 type 调用如下方法生成渲染对象:
// 如果节点是字符串或者数字
return ReactHostComponent.createInstanceForText( vdom(string|number) );
// 如果节点是宿主内置节点,譬如浏览器的 html 的节点
return ReactHostComponent.createInternalComponent( vdom );
// 如果是 React component 节点
return new ReactCompositeComponentWrapper( vdom );
- div 等 html 的原生 dom 节点对应的渲染对象是 ReactDOMComponent 的实例。如何把 { type:’div’, … } 生成一个 dom node 就在这个类(的 mountComponent 方法)里面。
class ReactDOMComponent {
constructor( vdom ) {
this._currentElement = vdom;
}
mountComponent() {
var result;
var props = this._currentElement.props;
if ( this._currentElement.type === 'div' ) {
result = document.createElement( 'div' );
for(var key in props ) {
result.setAttribute( key, props[ key ] );
}
} else {
// 其他类型
}
// 迭代子节点
props.children.forEach( child=>{
var childRenderedComponent = = instantiateReactComponent( child );
var childMarkup = childRenderedComponent.mountComponent();
result.appendChild( childMarkup );
} )
return result;
}
}
- 我们再看下 React component 的渲染对象 ReactCompositeComponentWrapper(主要实现在 ReactCompositeComponent 里面,ReactCompositeComponentWrapper 只是一个防止循环引用的 wrapper)
// 以下是伪代码
class ReactCompositeComponent {
_currentElement: vdom,
_rootNodeID: 0,
_compositeType:
_instance:
_hostParent:
_hostContainerInfo:
// See ReactUpdateQueue
_updateBatchNumber:
_pendingElement:
_pendingStateQueue:
_pendingReplaceState:
_pendingForceUpdate:
_renderedNodeType:
_renderedComponent:
_context:
_mountOrder:
_topLevelWrapper:
// See ReactUpdates and ReactUpdateQueue.
_pendingCallbacks:
// ComponentWillUnmount shall only be called once
_calledComponentWillUnmount:
// render to dom node
mountComponent( transaction, hostParent, hostContainerInfo, context ) {
// ---------- 初始化 React.Component --------------
var Component = this._currentElement.type;
var publicProps = this._currentElement.props;
/*
React.Component 组件有2种:
new Component(publicProps, publicContext, updateQueue);
new StatelessComponent(Component);
对应的 compositeType 有三种
this._compositeType = StatelessFunctional | PureClass | ImpureClass,
组件种类和 compositeType 在源码中都有区分,但是这里为了简单,只示例最常用的一种组件的代码
*/
var inst = new Component(publicProps, publicContext, updateQueue);
inst.props = publicProps;
inst.context = publicContext;
inst.refs = emptyObject;
inst.updater = updateQueue;
// 渲染对象存储组件对象
this._instance = inst;
// 通过 map 又把组件对象和渲染对象联系起来
ReactInstanceMap.set(inst, this);
/*
ReactInstanceMap: {
-----------------------------------------------
| |
v |
React.Component: ReactCompositeComponentWrapper { |
_instance: <-------------------------------------
}
}
这样双方都在需要对方的时候可以获得彼此的引用
*/
// ---------- 生成 React.Component 的 dom --------------
// 组件生命周期函数 componentWillMount 被调用
inst.componentWillMount();
// 调用 render 方法返回组件的虚拟 dom
var renderedElement = inst.render();
// save nodeType
var nodeType = ReactNodeTypes.getType(renderedElement);
this._renderedNodeType = nodeType;
// 根据组件的虚拟 dom 生成渲染对象
var child = instantiateReactComponent(renderedElement)
this._renderedComponent = child;
// 生成真正的 dom node
// 其实源码中的真正代码应该是 var markup = ReactReconciler.mountComponent( child, ... ),
// 这里为了简化说明,先不深究 ReactReconciler.mountComponent 还做了点什么
var markup = child.mountComponent();
// 把组件生命周期函数 componentDidMount 注册到回调函数中,当整个 dom node tree 被添加到容器节点后触发。
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
return markup;
}
}
// static member
ReactCompositeComponentWrapper._instantiateReactComponent = instantiateReactComponent
最终流程
jsx –babel–> react.createElement() 打包阶段生成 —> 渲染阶段 createElement 执行 生成虚拟dom
–> vdom 生成渲染对象
ReactDOMComponent -> <span/>
ReactCompositeComponentWrapper.render() -> vdom -> instantiateReactComponent(vdom) -> <form><input/></from>
–> 挂载 (containerDomNode.appendChild( domNode );)