React 渲染过程
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'))
React.createElement( 'div',{ className: 'test' },React.createElement( 'span',{ onClick: function(){} },'CLICK ME' ),React.createElement(Form,null) )
{ type: 'div',props: { className: 'test',children: [] } } 除了一些 dom 相关的属性,虚拟 dom 对象还包括些 React 自身需要的属性,譬如:ref,key。最终示例中的 jsx 生成出来的虚拟 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' }
// vdom 是第3步生成出来的虚拟 dom 对象 var renderedComponent = instantiateReactComponent( vdom ); // dom node var markup = renderedComponent.mountComponent(); // 把生成的 dom node 插入到容器 node 里面,真正在页面上显示出来 // 下面是伪代码,React 的 dom 操作封装在 DOMLazyTree 里面 containerNode.appendChild( markup ); instantiateReactComponent 传入的是虚拟 dom 节点,这个方法做的就是根据不同的 type 调用如下方法生成渲染对象: // 如果节点是字符串或者数字 return ReactHostComponent.createInstanceForText( vdom(string|number) ); // 如果节点是宿主内置节点,譬如浏览器的 html 的节点 return ReactHostComponent.createInternalComponent( vdom ); // 如果是 React component 节点 return new ReactCompositeComponentWrapper( vdom ); ReactHostComponent.createXXX 也只是一层抽象,不是最终的的渲染对象,这层抽象屏蔽了宿主。譬如手机端(React native)和浏览器中同样调用 ReactHostComponent.createInternalComponent( vdom ); 他生成的最终的渲染对象是不同的,我们当前只讨论浏览器环境。字符串和数字没有什么悬念,在这里我们就不深入探讨了,再进一步看,div 等 html 的原生 dom 节点对应的渲染对象是 ReactDOMComponent 的实例。如何把 { type:'div',... } 生成一个 dom node 就在这个类(的 mountComponent 方法)里面。(对如何生成 div、span、input、select 等 dom node 感兴趣的可以去探究 ReactDOMComponent,这里不做具体的讨论,本文只是想总结下 React 整个渲染过程。下面只给出一个最简示例代码:) 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,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
<div className="test"> <span onClick={this.click}>CLICK ME</span> <Form/> </div> | { type: 'div',props: { children: },key: 'xxx' } | domNode = { ReactDOMComponent -> <div/> props: { children: [ ReactDOMComponent -> <span/> ReactCompositeComponentWrapper.render() -> vdom -> instantiateReactComponent(vdom) -> <form><input/></from> ] } } | 以上是 React 渲染 dom 的一个基本流程,下一篇计划总结下更新 dom 的流程,即 setState 后发生了什么。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |