从react源码看Virtual Dom到真实Dom的渲染过程

很多人都看过许多React的Virtual Dom的文章,背熟了好多生命周期函数,然而,对于一个Virtual Dom渲染成一个真实Dom的过程你是否真的研究过呢?

ReactDOM.render(
    <h1>Hello World</h1>, 
    document.getElementById('root')
);

以上代码用babel转义过来就是:

ReactDOM.render(React.createElement(
    'h1',
    null,
    'Hello World'
), document.getElementById('root'));

ReactElement就是我们常说的Virtual Dom,下面我们将讨论react将一个ReactElement渲染成真实Dom的过程。

我们先看一下ReactElement.js源码:链接

var ReactElement = function(type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allow us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };
  return element;
ReactElement.createElement = function(type, config, children) {
  var propName;

  // Reserved names are extracted
  var props = {};

  var key = null;
  var ref = null;
  var self = null;
  var source = null;
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
};

以下是我们创建的Hello World的ReactElement


{
    $$typeof: Symbol(react.element)
    key: null
    props: {children: "Hello World"}
    ref: null
    type: "h1"
    _owner: null
    _store: {validated: false}
    _self: null
    _source: null
    __proto__: Object
}

接下来我们看render函数源码:链接

在render里面,调用_renderSubtreeIntoContainer,_renderSubtreeIntoContainer里又调用_renderNewRootComponent,_renderNewRootComponent生成一个ReactCompositeComponentWrapper并返回。

一个很重要的数据结构:ReactCompositeComponentWrapper

instantiateReactComponent方法本质上是个工厂函数,它在内部会对ReactElement类型进行判断,返回一个ReactCompositeComponentWrapper。

我么来看instantiateReactComponent源码:

function instantiateReactComponent(node, shouldHaveDebugID) {
  var instance;
  if (node === null || node === false) {
    // 1 ReactDOMEmptyComponent:空对象
    instance = ReactEmptyComponent.create(instantiateReactComponent);
  } else if (typeof node === 'object') {
    if (typeof element.type === 'string') {
        // 2 ReactDOMComponent:DOM原生对象
      instance = ReactHostComponent.createInternalComponent(element);
    } else if (isInternalComponentType(element.type)) {
      instance = new element.type(element);
      if (!instance.getHostNode) {
        instance.getHostNode = instance.getNativeNode;
      }
    } else {
        // 3 ReactCompositeComponent:React自定义对象
      instance = new ReactCompositeComponentWrapper(element);
    }
  } else if (typeof node === 'string' || typeof node === 'number') {
    // 4 ReactDOMTextComponent:文本对象
    instance = ReactHostComponent.createInstanceForText(node);
  } else {
    !false ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Encountered invalid React node of type %s', typeof node) : _prodInvariant('131', typeof node) : void 0;
    }
  return instance;
}

一个ReactCompositeComponentWrapper长这样:

{
    _calledComponentWillUnmount: false
    _compositeType: null
    _context: null
    // 包了一层的ReactElement
    _currentElement: {$$typeof: Symbol(react.element), key: null, ref: null, props: {…}, type: ƒ, …}
    _debugID: 0
    _hostContainerInfo: null
    _hostParent: null
    _instance: null
    _mountImage: null
    _mountIndex: 0
    _mountOrder: 0
    _pendingCallbacks: null
    _pendingElement: null
    _pendingForceUpdate: false
    _pendingReplaceState: false
    _pendingStateQueue: null
    _renderedComponent: null
    _renderedNodeType:  null
    _rootNodeID: 0
    _topLevelWrapper: null
    _updateBatchNumber: null
    _warnedAboutRefsInRender: false
    __proto
}

接下来是我认为最复杂的一部分,也就是负责将ReactCompositeComponentWrapper渲染成真实Dom的部分

_renderNewRootComponent源码:

_renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
    // Various parts of our code (such as ReactCompositeComponent's
    // _renderValidatedComponent) assume that calls to render aren't nested;
    // verify that that's the case.
    process.env.NODE_ENV !== 'production' ? warning(ReactCurrentOwner.current == null, '_renderNewRootComponent(): Render methods should be a pure function ' + 'of props and state; triggering nested component updates from ' + 'render is not allowed. If necessary, trigger nested updates in ' + 'componentDidUpdate. Check the render method of %s.', ReactCurrentOwner.current && ReactCurrentOwner.current.getName() || 'ReactCompositeComponent') : void 0;

    !isValidContainer(container) ? process.env.NODE_ENV !== 'production' ? invariant(false, '_registerComponent(...): Target container is not a DOM element.') : _prodInvariant('37') : void 0;

    ReactBrowserEventEmitter.ensureScrollValueMonitoring();
    var componentInstance = instantiateReactComponent(nextElement, false);

    // The initial render is synchronous but any updates that happen during
    // rendering, in componentWillMount or componentDidMount, will be batched
    // according to the current batching strategy.

    ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);

    var wrapperID = componentInstance._instance.rootID;
    instancesByReactRootID[wrapperID] = componentInstance;

    return componentInstance;
  }

componentInstance是由instantiateReactComponent生成的一个ReactCompositeComponentWrapper。

在ReactUpdates.batchedUpdates里面,通过mountComponent方法将ReactCompositeComponentWrapper转化为一个markup
一个markup长这样:

{
    children:[]
    html:null
    node:h1
    text:null
    toString:ƒ toString()
    __proto__:Object
}

markup通过mountComponentIntoNode、_mountImageIntoNode最终渲染为真实Dom!!!

知道了这么个过程,也许对后面研究生命周期,diff函数有很大的帮助吧。

相关链接:React源码系列之初次渲染

React源码分析2 — 组件和对象的创建(createClass,createElement)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容