Viewing Virtual dom of Vue Source from Debugging Tools

Preface

Last time we mentioned that in the case of a child component, the parent component generates an instance of the child component after the created hook function is executed, the child component executes the created hook function, and checks if there are also child components, or repeats the steps of the parent component, otherwise the child component's dom element renders

Learn more about vnode

In the last article Article In fact, we mentioned a function--createComponentInstanceForVnode.

function createComponentInstanceForVnode (
  vnode, // we know it's MountedComponentVNode but flow doesn't
  parent // activeInstance in lifecycle state
) {
  var options = {
    _isComponent: true,
    _parentVnode: vnode,
    parent: parent
  };
  // check inline-template render functions
  var inlineTemplate = vnode.data.inlineTemplate;
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render;
    options.staticRenderFns = inlineTemplate.staticRenderFns;
  }
  return new vnode.componentOptions.Ctor(options)
}

Code associated with it_

...
var child = vnode.componentInstance = createComponentInstanceForVnode(
    vnode,
    activeInstance
);
child.$mount(hydrating ? vnode.elm : undefined, hydrating);

We can learn from this:

  • Instances of subcomponents are generated by createComponentInstanceForVnode
  • The above conclusions relate to vnode.componentOptions.Ctor(options)

VNode

By retrieving componentOptions globally, the following code exists_

var VNode = function VNode (
  tag,
  data,
  children,
  text,
  elm,
  context,
  componentOptions,
  asyncFactory
) {
  this.tag = tag;
  this.data = data;
  this.children = children;
  this.text = text;
  this.elm = elm;
  ...
}

In fact, there's a strange piece of code between the beforeMount hook and the mounted hook_

new Watcher(vm, updateComponent, noop, {
    before: function before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);

Looking at the previous article, you already know Watcher's execution logic:

  1. Initialize related properties, including getter properties
  2. Execute getter while assigning value

Attach the updateComponent implementation:

updateComponent = function () {
    vm._update(vm._render(), hydrating);
};

This means that the function updateComponent will be executed with such a call sequence (top-down):

  • vm._render
  • vm_update

dom elements must also be rendered on both function calls

vm._render

Vue.prototype._render = function () {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    var _parentVnode = ref._parentVnode;

    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      );
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode;
    // render self
    var vnode;
    try {
      // There's no need to maintain a stack becaues all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm;
      vnode = render.call(vm._renderProxy, vm.$createElement);
    } catch (e) {
      handleError(e, vm, "render");
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
        } catch (e) {
          handleError(e, vm, "renderError");
          vnode = vm._vnode;
        }
      } else {
        vnode = vm._vnode;
      }
    } finally {
      currentRenderingInstance = null;
    }
    // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0];
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        );
      }
      vnode = createEmptyVNode();
    }
    // set parent
    vnode.parent = _parentVnode;
    return vnode
  };

Comb the execution of function_render (top-down):

  • Assign_parentVnode (parent component's vnode) to vm.$vnode
  • Execute normallizeScopedSlots to merge $slots of parent and child components with $scopedSlots
  • Execute render function and assign to vnode (that is, get existing vnode)
  • Execute the createEmptyVNode function if the vnode is empty
  • Return vnode

Here we give priority to breakpoints in render functions, which naturally result in the following execution processes:

  • render
  • vm.$createElement

Since new Vue({...}) was first executed, it looks like the breakpoint stopped at "strange place"

new Vue({
  render: h => h(App),
}).$mount('#app')

render function

Careful classmates will notice such a line of code.

vnode = render.call(vm._renderProxy, vm.$createElement);

Breakpoint jumps here immediately after step_

new Vue({
    render: h => h(App),
    ...
}).$mount('#app')

In fact, here vm._renderProxy is used as the context object for the render function, and vm.$createElement returns a closure function as an argument to the render function

Related code:

vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };

summary

Summarize the complete logic for generating vnode s:

  • Execute the $mount function
  • Determines whether or not in the browser environment, then gets the dom element and assigns it to the el variable, otherwise the el variable takes undefined
  • Execute mountComponent function
  • Execute new Watcher(vm, updateComponent, noop,...)
  • Because of Watcher's "attributes" (the incoming updateComponent is assigned to the getter and executed after), the _render function is triggered after that
  • Execute $createElement
  • Execute createElement
  • Execute_createElement
  • To determine if the parameter data and data.is not empty, assign data.is to tag
  • If the tag is empty, then this is considered a blank node, call createEmptyVNode to create a "blank node" and mark isComment as true
  • Determine if the tag is a "reserved word" or an HTML tag, generating the corresponding vnode, otherwise call the createComponent function to generate the corresponding vnode?
  • Return to vnode last

correlation function

createEmptyVNode

var createEmptyVNode = function (text) {
  if ( text === void 0 ) text = '';

  var node = new VNode();
  node.text = text;
  node.isComment = true;
  return node
};

Keywords: Javascript Vue

Added by jvquach on Wed, 15 May 2019 13:43:00 +0300