React 渲染過程

  1. 程序假設有以下 jsxjavascript

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 後獲得以下代碼:html

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 是:java

{
  type: 'div',
  props: {
    className: 'test',
    children: []
  }
}

除了一些 dom 相關的屬性,虛擬 dom 對象還包括些 React 自身須要的屬性,譬如:ref,key。最終示例中的 jsx 生成出來的虛擬 dom,大體以下:node

{
  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,最終插入容器節點。查看源碼你會發現以下代碼:api

// 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 整個渲染過程。下面只給出一個最簡示例代碼:babel

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 只是一個防止循環引用的 wrapperapp

// 如下是僞代碼
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. 最終的過程是:less

<div className="test">
  <span onClick={this.click}>CLICK ME</span>
  <Form/>
</div>

  |
babel and React.createElement
  |
  vdom

{
  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 後發生了什麼。

相關文章
相關標籤/搜索