Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 渲染过程 #9

Open
sixwinds opened this issue Nov 15, 2016 · 0 comments
Open

React 渲染过程 #9

sixwinds opened this issue Nov 15, 2016 · 0 comments

Comments

@sixwinds
Copy link
Owner

sixwinds commented Nov 15, 2016

React 渲染过程

  1. 程序假设有如下 jsx
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. 拿 ReactDOM render 的部分(<div className="test">...</div>)为例,用 babel 把 jsx 转成 js 后得到如下代码:
React.createElement( 'div', {
  className: 'test'
  },
  React.createElement( 'span',
    { onClick: function(){} },
    'CLICK ME'
  ),
  React.createElement(Form, null)
)
  1. 这里看下 API: React.createElement(component, props, ...children)。它生成一个 js 的对象,这个对象是用来代表一个真实的 dom node,这个 js 的对象就是我们俗称的虚拟dom。(虚拟 dom 的意思是用 js 对象结构模拟出 html 中 dom 结构,批量的增删改查先直接操作 js 对象,最后更新到真正的 dom 树上。因为直接操作 js 对象的速度要比操作 dom 的那些 api 要快。) 譬如根元素 <div className="test"></div> 生成对应出来的虚拟 dom 是:
{
  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'
}
  1. 有了虚拟 dom,接下来的工作就是把这个虚拟 dom 树真正渲染成一个 dom 树。React 的做法是针对不同的 type 构造相应的渲染对象,渲染对象提供一个 mountComponent 方法(负责把对应的某个虚拟 dom 的节点生成成具体的 dom node),然后循环迭代整个 vdom tree 生成一个完整的 dom node tree,最终插入容器节点。查看源码你会发现如下代码:
// 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, 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
  1. 最终的过程是:
<div className="test">
  <span onClick={this.click}>CLICK ME</span>
  <Form/>
</div>

  |
babel and React.createElement
  |
  v

{
  type: 'div',
  props: {
    className: 'xxx',
    children: [ {
      type: 'span',
      props: {
        children:
      },
      ref:
      key:
    }, {
      type: Form,
      props: {
        children:
      },
      ref:
      key:
    } ] | Element
  }
  ref: 'xxx',
  key: 'xxx'
}

  |
var domNode = new ReactDOMComponent( vdom ).mountComponent();
  |
  v

domNode = {
  ReactDOMComponent -> <div/>
  props: {
    children: [
      ReactDOMComponent -> <span/>
      ReactCompositeComponentWrapper.render() -> vdom -> instantiateReactComponent(vdom) -> <form><input/></from>
    ]
  }
}

  |
  |
  v
containerDomNode.appendChild( domNode );

以上是 React 渲染 dom 的一个基本流程,下一篇计划总结下更新 dom 的流程,即 setState 后发生了什么。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant