The biggest upgrade to Vue 2.0 over Vue 1.0 is the use of the virtual DOM. Updates to views in Vue 1.0 are purely reactive. During reactive initialization, a deP is created for a reactive data key, and several Watchers are created for each key referenced in the template. That is, a key corresponds to a DEP, which manages one or more watcher. Due to the one-to-one relationship between Watcher and DOM, a DOM can be explicitly updated during update, and the update efficiency is very high.

As applications get larger and more components, a single component can have a lot of Watcher, and performance becomes an issue. With the addition of the virtual DOM and diff in Vue 2.0, a component only needs to create a Watcher, using reactive + DIff updates, reactive between components, and diff within components. When the data changes, watcher is notified of the update, that is, the entire component is notified of the update. The specific element and location of the update can be obtained through the diff algorithm comparing the old and new virtual DOM, and the difference is actually updated to the view.

Virtual DOM is used in React and Vue. On the one hand, it can improve performance, and on the other hand, it can be better cross-platform, such as server rendering.

The virtual DOM, also known as a VNode, essentially uses JS objects to simulate nodes that exist in the real DOM. Here’s an example:

<div id="app">
  <h1>Virtual DOM<h1>
</div>
Copy the code
{
  tag"div".attrs: {
    id"app"
  },
  children: [{tag"h1".text"Virtual DOM"}}]Copy the code

VNode

The virtual DOM in Vue is described by a Class. Let’s take a look first:

// src/core/vdom/vnode.js
export default class VNode {
  tagstring | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void// rendered in this component's scope
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void// component instance
  parent: VNode | void// component placeholder node
  // strictly internal
  raw: boolean// contains raw HTML? (server only)
  isStatic: boolean// hoisted static node
  isRootInsert: boolean// necessary for enter transition check
  isComment: boolean// empty comment placeholder?
  isCloned: boolean// is a cloned node?
  isOnce: boolean// is a v-once node?
  asyncFactory: Function | void// async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  fnContext: Component | void// real context vm for functional nodesfnOptions: ? ComponentOptions;// for SSR caching
  devtoolsMeta: ?Object// used to store functional render context for devtools
  fnScopeId: ?string// functional scope id support
  constructor (tag? :string, data? : VNodeData, children? :?Array<VNode>, text? :string, elm? : Node, context? : Component, componentOptions? : VNodeComponentOptions, asyncFactory? :Function
  ) {
    this.tag = tag
    this.data = data
    this.children = children
    this.text = text
    this.elm = elm
    this.ns = undefined
    this.context = context
    this.fnContext = undefined
    this.fnOptions = undefined
    this.fnScopeId = undefined
    this.key = data && data.key
    this.componentOptions = componentOptions
    this.componentInstance = undefined
    this.parent = undefined
    this.raw = false
    this.isStatic = false
    this.isRootInsert = true
    this.isComment = false
    this.isCloned = false
    this.isOnce = false
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }
  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next */
  get child (): Component | void {
    return this.componentInstance
  }
}
Copy the code

_render

As mentioned earlier, when reactive data changes, triggering an update simply executes the following code:

updateComponent = () = > {
  vm._update(vm._render(), hydrating)
}
Copy the code

Execute vm._render() to get the VNode and pass it as a parameter to vm._update to update the view. Let’s look at the definition of vm._render() :

// src/core/instance/render.js
export function renderMixin (Vue: Class<Component>{
  // install runtime convenience helpers
  // Mount some run-time utility methods on the component instance
  installRenderHelpers(Vue.prototype)
  Vue.prototype.$nextTick = function (fn: Function{
    return nextTick(fn, this)}/** * Generate VNode by executing render function * but add a lot of exception handling code */
  Vue.prototype._render = function () :VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options
    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.
    // Set the parent vnode. This allows the rendering function to access the data on the placeholder node.
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // There's no need to maintain a stack because all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm
      // Execute render function to generate vNode
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      handleError(e, vm, `render`)
      // Error handling, the development environment renders the error message, production environment returns the previous VNode, to prevent rendering errors resulting in blank components
      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 a vNode is an array and has only one entry, place that entry directly back
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]}// The render function returns an empty vnode when it fails
    if(! (vnodeinstanceof 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
  }
}
Copy the code

The render method is called in _render, in two cases:

  • Render compiled from the template
with(this){return _c(tag, data, children, normalizationType)}
Copy the code
  • User defined render
render: function (createElement) {
  return createElement('div', {
     attrs: {
        id: 'app'}},this.msg)
}
Copy the code

$createElement = vm.$createElement = vm.$createElement = vm.

// src/core/instance/render.js
export function initRender (vm: Component{
/ * * *@param {*} A Label name *@param {*} B JSON string * for the property@param {*} C Array of child nodes *@param {*} The normalized type of the d node *@returnsVNode or Array<VNode>
 */
  vm._c = (a, b, c, d) = > createElement(vm, a, b, c, d, false)
  vm.$createElement = (a, b, c, d) = > createElement(vm, a, b, c, d, true)}Copy the code

These two methods support the same parameters and actually call the createElement method.

createElement

// src/core/vdom/create-element.js
// A vNode that generates components or common labels, a wrapper function that provides a more flexible interface.
export function createElement (
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
) :VNode | Array<VNode{
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  // Execute the _createElement method to create a VNode for the component
  return _createElement(context, tag, data, children, normalizationType)
}
Copy the code

_createElement

// src/core/vdom/create-element.js
export function _createElement (context: Component, tag? :string | Class<Component> | Function | Object, data? : VNodeData, children? :any, normalizationType? :number
) :VNode | Array<VNode{
  if (isDef(data) && isDef((data: any).__ob__)) {
    // If data is a responsive object, return the Vnode of the empty nodeprocess.env.NODE_ENV ! = ='production' && warn(
      `Avoid using observed data object as vnode data: The ${JSON.stringify(data)}\n` +
      'Always create fresh vnode data objects in each render! ',
      context
    )
    return createEmptyVNode()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if(! tag) {// Dynamic component: when the IS property is set to false
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  // warn against non-primitive key
  if(process.env.NODE_ENV ! = ='production'&& isDef(data) && isDef(data.key) && ! isPrimitive(data.key) ) {if(! __WEEX__ || ! ('@binding' in data.key)) {
      warn(
        'Avoid using non-primitive value as key, ' +
        'use string/number value instead.',
        context
      )
    }
  }
  // support single function children as default scoped slot
  // The child node array has only one function, slot it by default, and then empty its own child node array
  if (Array.isArray(children) &&
    typeof children[0= = ='function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  // The child node is normalized
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // Platform built-in nodes
      // platform built-in elements
      if(process.env.NODE_ENV ! = ='production'&& isDef(data) && isDef(data.nativeOn) && data.tag ! = ='component') {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>. `,
          context
        )
      }
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined.undefined, context
      )
    } else if((! data || ! data.pre) && isDef(Ctor = resolveAsset(context.$options,'components', tag))) {
      // Tag is a custom component
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      // Unknown tag tag, checked at run time because it may be assigned a namespace when its parent normalizes its children
      vnode = new VNode(
        tag, data, children,
        undefined.undefined, context
      )
    }
  } else {
    // Tag is not a string
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  if (Array.isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}
Copy the code

Createelement (VNode, VNode, VNode); createElement (VNode, VNode, VNode, VNode, VNode, VNode, VNode);

  • If it’s a platform built in node, create a normal VNode.
  • If it is a registered component name, passcreateComponentCreate a VNode of component type.
  • Otherwise, create a VNode with an unknown label name.

Another point worth noting here is the normalization of the child nodes with the normalizeChildren and simpleNormalizeChildren methods so that each node is of type VNode.

The simpleNormalizeChildren method call scenario is that the Render function is compiled. The functional Component returns an array instead of a root node. So the array.prototype. concat method is used to flatten the entire children Array so that it is only one layer deep.

The normalizeChildren method can be called in two scenarios. One scenario is that the render function is written by the user, and when children have only one node, Vue.js allows the user to write children as a basic type to create a single simple text node at the interface level. In this case, createTextVNode is called to create a VNode of the text node. Another scenario is when a nested array is generated when slot, V-for is compiled, and the normalizeArrayChildren method is called

Component type

createComponent

// src/core/vdom/create-component.js
export function createComponent (
  Ctor: Class<Component> | Function | Object | void, data: ? VNodeData, context: Component, children: ?Array<VNode>, tag? :string
) :VNode | Array<VNode> | void {
  // The component's constructor does not exist
  if (isUndef(Ctor)) {
    return
  }
  // context.$options._base = Vue.options._base = Vue
  const baseCtor = context.$options._base
  // vue. extend is defined in SRC /core/global-api/extend.js
  // When Ctor is a configuration object, construct a subclass of Vue by vue. extend
  Vue. Extend is not executed when Ctor is a functional, indicating an asynchronous component
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }
  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeofCtor ! = ='function') {
    if(process.env.NODE_ENV ! = ='production') {
      warn(`Invalid Component definition: The ${String(Ctor)}`, context)
    }
    return
  }
  // Async component
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    // If Ctor. Cid is null, then Ctor is a function indicating that this is an asynchronous component
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      // Returns a placeholder node for the asynchronous component, which is rendered as an annotation node but retains all the original information of the node, which will be used for asynchronous server rendering and hydration
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }
  // Node properties JSON String
  data = data || {}
  // Merge option that resolves constructor options in case global blending is applied after component constructor creation
  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor)
  // Convert the component's V-model to attributes and values of the data.attrs object and events and callbacks on the data.on object
  // transform component v-model data into props & events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }
  PropsData [key] = val
  // extract props
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  // functional Component (functional Component)
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }
  Extract event listeners, because these listeners need to be treated as child component listeners, not DOM listeners
  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  // Replace it with a listener with a. Native modifier so that it can be processed during the parent component patch.
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn
  if (isTrue(Ctor.options.abstract)) {
    // Abstract components retain only props, Listeners, and slots
    // abstract components do not keep anything
    // other than props & listeners & slot
    // work around flow
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }
  // Install component hook functions init, prepatch, insert, destroy
  // install component management hooks onto the placeholder node
  installComponentHooks(data)
  // Instantiate a VNode with new VNode and return
  const name = Ctor.options.name || tag
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? ` -${name}` : ' '}`,
    data, undefined.undefined.undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  // Weex specific: invoke recycle-list optimized @render function for
  // extracting cell-slot template.
  // https://github.com/Hanks10100/weex-native-directive/tree/master/component
  /* istanbul ignore if */
  if (__WEEX__ && isRecyclableComponent(vnode)) {
    return renderRecyclableComponentTemplate(vnode)
  }
  return vnode
}
Copy the code

As you can see from the code above, asynchronous components, functional components, and plain components are treated separately. In terms of common components, the createComponent method does a few things:

  • throughVue.extendConstructor to build a subclass.
  • Install component hook functionsinit,prepatch,insert,destroy.
  • Instantiate VNode and return VNode.

The logic inside createComponent is a bit complicated, so let’s look at the generic components first, in several component categories:

resolveConstructorOptions

// src/core/instance/init.js
/ * * *@description: Parses configuration object options from the component constructor and merges base class options *@param {*} Ctor
 * @return {Object} options* /
export function resolveConstructorOptions (Ctor: Class<Component>{
  // Get options from the instance constructor
  let options = Ctor.options
  if (Ctor.super) {
    // Base class exists, recursively resolving base class constructor options
    const superOptions = resolveConstructorOptions(Ctor.super)
    / / cache
    const cachedSuperOptions = Ctor.superOptions
    if(superOptions ! == cachedSuperOptions) {// Indicates that the base class configuration item has changed
      Ctor.superOptions = superOptions
      // Find the option to change
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // If there are options that have been modified or added, merge the two options
      if (modifiedOptions) {
        // Merge the changed options with the extend options
        extend(Ctor.extendOptions, modifiedOptions)
      }
      // Assign the new option to options
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}
Copy the code

resolveModifiedOptions

// src/core/instance/init.js
/ * * *@descriptionResolves subsequent changes or additions to the constructor option *@param {*} Ctor
 * @return {*} Modified Inconsistent option */
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
  let modified
  // Constructor options
  const latest = Ctor.options
  // Sealed constructor option, backup
  const sealed = Ctor.sealedOptions
  // Compare the two options and record the inconsistent options
  for (const key in latest) {
    if(latest[key] ! == sealed[key]) {if(! modified) modified = {} modified[key] = latest[key] } }return modified
}
Copy the code

transformModel

// src/core/vdom/create-component.js
/** * Transform component V-model info (value and callback) /** * Transform component V-model info (value and callback) into * prop and event handler respectively. */
function transformModel (options, data: any{
  // Attributes and events for model, default to value and input
  const prop = (options.model && options.model.prop) || 'value'
  const event = (options.model && options.model.event) || 'input'
    // Store v-model values in the data.attrs object
    ; (data.attrs || (data.attrs = {}))[prop] = data.model.value
  // Store v-model events on the data.on object
  const on = data.on || (data.on = {})
  // An existing event callback function
  const existing = on[event]
  // The event callback function in the V-model
  const callback = data.model.callback
  // merge callback functions
  if (isDef(existing)) {
    if (
      Array.isArray(existing)
        ? existing.indexOf(callback) === -1: existing ! == callback ) { on[event] = [callback].concat(existing) } }else {
    on[event] = callback
  }
}
Copy the code

extractPropsFromVNodeData

// src/core/vdom/helpers/extract-props.js
export function extractPropsFromVNodeData (data: VNodeData, Ctor: Class<Component>, tag? :string
): ?Object {
  // Only raw values are extracted, validation and default values are handled in child components
  // we are only extracting raw values here.
  // validation and default values are handled in the child
  // component itself.
  const propOptions = Ctor.options.props
  // Undefined props
  if (isUndef(propOptions)) {
    return
  }
  const res = {}
  const { attrs, props } = data
  if (isDef(attrs) || isDef(props)) {
    / / traverse propsOptions
    for (const key in propOptions) {
      // Convert the small hump key to xxx-xxx-xxx
      const altKey = hyphenate(key)
      if(process.env.NODE_ENV ! = ='production') {
      // Error message
        const keyInLowerCase = key.toLowerCase()
        if( key ! == keyInLowerCase && attrs && hasOwn(attrs, keyInLowerCase) ) { tip(`Prop "${keyInLowerCase}" is passed to component ` +
            `${formatComponentName(tag || Ctor)}, but the declared prop name is` +
            `"${key}". ` +
            `Note that HTML attributes are case-insensitive and camelCased ` +
            `props need to use their kebab-case equivalents when using in-DOM ` +
            `templates. You should probably use "${altKey}" instead of "${key}". `
          )
        }
      }
      checkProp(res, props, key, altKey, true) ||
        checkProp(res, attrs, key, altKey, false)}}return res
}
Copy the code

checkProp

// src/core/vdom/helpers/extract-props.js
function checkProp (
  res: Object,
  hash: ?Object,
  key: string,
  altKey: string,
  preserve: boolean
) :boolean {
  if (isDef(hash)) {
    // Determine if there is a key or altKey in the hash (props/attrs) object
    // If yes, set it to res => res[key] = hash[key]
    if (hasOwn(hash, key)) {
      res[key] = hash[key]
      if(! preserve) {delete hash[key]
      }
      return true
    } else if (hasOwn(hash, altKey)) {
      res[key] = hash[altKey]
      if(! preserve) {delete hash[altKey]
      }
      return true}}return false
}
Copy the code

installComponentHooks

InstallComponentHooks merges componentVNodeHooks into data.hook. If a hook exists, execute mergeHook. The VNode executes related hook functions during patch execution.

// src/core/vdom/create-component.js
Init, prepatch, insert, destroy; / / Create, update, destroy; / / Create, update, destroy
function installComponentHooks (data: VNodeData{
  const hooks = data.hook || (data.hook = {})
  // Walk through the hooksToMerge array, hooksToMerge = ['init', 'prepatch', 'insert' 'destroy']
  for (let i = 0; i < hooksToMerge.length; i++) {
    // key = init
    const key = hooksToMerge[i]
    // Get the method corresponding to the key from the data.hook object
    const existing = hooks[key]
    // Method for the key object in the componentVNodeHooks object
    const toMerge = componentVNodeHooks[key]
    // Merge the user-passed hook method with the framework's own hook method
    if(existing ! == toMerge && ! (existing && existing._merged)) { hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge } } }function mergeHook (f1: any, f2: any) :Function {
  const merged = (a, b) = > {
    // flow complains about extra args which is why we use any
    f1(a, b)
    f2(a, b)
  }
  merged._merged = true
  return merged
}
Copy the code

componentVNodeHooks

// src/core/vdom/create-component.js
// Inline hook called on component VNode during patch
// inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
  / / initialization
  init (vnode: VNodeWithData, hydratingboolean): ?boolean {
    if( vnode.componentInstance && ! vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) {// The component wrapped by keep-alive
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      / / create the component instances, namely the new vnode.com ponentOptions. Ctor (options) = > get Vue component instance
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      // Call the $mount method to enter the mount phase.
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },
  / / update the VNode
  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    // New VNode configuration item
    const options = vnode.componentOptions
    // Old VNode component instance
    const child = vnode.componentInstance = oldVnode.componentInstance
    Update the old with the new
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children)},// Execute the component's mounted hook function
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if(! componentInstance._isMounted) { componentInstance._isMounted =true
      callHook(componentInstance, 'mounted')}// Handle the keep-alive exception
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        // vue-router#1212
        // During updates, a kept-alive component's child components may
        // change, so directly walking the tree here may call activated hooks
        // on incorrect children. Instead we push them into a queue which will
        // be processed after the whole patch process ended.
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)}}},/ / destroy
  destroy (vnode: MountedComponentVNode) {
    // Get the component instance
    const { componentInstance } = vnode
    // Destroyed components skipped
    if(! componentInstance._isDestroyed) {if(! vnode.data.keepAlive) {// Call $destroy directly to destroy the component
        componentInstance.$destroy()
      } else {
        // The component wrapped by keep-alive
        // Cache the state of the component by deactivating the component without destroying the component instance
        deactivateChildComponent(componentInstance, true /* direct */)}}}}Copy the code

createComponentInstanceForVnode

// src/core/vdom/create-component.js
. / / the new vnode.com ponentOptions Ctor (options) = > get Vue component instance
export function createComponentInstanceForVnode (
  // we know it's MountedComponentVNode but flow doesn't
  vnode: any.// activeInstance in lifecycle state
  parent: any
) :Component {
  const options: InternalComponentOptions = {
    _isComponenttrue._parentVnode: vnode,
    parent
  }
  // Check the inline template rendering function
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  // new VueComponent(options) => Vue instance
  return new vnode.componentOptions.Ctor(options)
}
Copy the code

Finally, instantiate the VNode and return the VNode. In addition to normal components, there are branches: asynchronous components and functional components. Let’s look at the flow of asynchronous and functional components:

Asynchronous components

SRC /core/vdom/create-component.js -- in the createComponent method
// Async component
let asyncFactory
if (isUndef(Ctor.cid)) {
  asyncFactory = Ctor
  Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
  if (Ctor === undefined) {
    // return a placeholder node for async component, which is rendered
    // as a comment node but preserves all the raw information for the node.
    // the information will be used for async server-rendering and hydration.
    // Returns a placeholder node for the asynchronous component, which is rendered as an annotation node but retains all the original information of the node, which will be used for asynchronous server rendering and hydration
    return createAsyncPlaceholder(
      asyncFactory,
      data,
      context,
      children,       
      tag
    )
  }
}
Copy the code

resolveAsyncComponent

// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>
) :Class<Component> | void {
  if (isTrue(factory.error) && isDef(factory.errorComp)) {
    return factory.errorComp
  }
  if (isDef(factory.resolved)) {
    return factory.resolved
  }
  // Owner instance collection container, the reference to the same asynchronous component does not need to be resolved many times, but the instance that currently uses the asynchronous component is collected, after the asynchronous component is resolved, notify rendering update one by one
  const owner = currentRenderingInstance
  if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
    // already pending
    factory.owners.push(owner)
  }
  if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
    return factory.loadingComp
  }
  / /...
  // This bit of code is a bit long, so we'll break it down into regular asynchronous components, Promise asynchronous components, and advanced asynchronous components
}
Copy the code

In the case of ordinary functions, the previous if judgments can be ignored because they are used for advanced components. In the case of factory.xxxxx, it is considered that an asynchronous component should be initialized in multiple places at the same time, so it should actually be loaded only once.

createAsyncPlaceholder

// src/core/vdom/helpers/resolve-async-component.js
// Returns a placeholder node for the asynchronous component, which is rendered as a comment node but retains all of the node's original information
export function createAsyncPlaceholder (
  factory: Function, data: ? VNodeData, context: Component, children: ?Array<VNode>,
  tag: ?string
) :VNode {
  const node = createEmptyVNode()
  node.asyncFactory = factory
  node.asyncMeta = { data, context, children, tag }
  return node
}
Copy the code

Common asynchronous component

Vue.component('async-example'.function (resolve, reject) {
   // This particular require syntax tells Webpack
   // Automatically split compiled code into different blocks,
   // These blocks will be downloaded automatically through Ajax requests.
   require(['./my-async-component'], resolve)
   }
 )
Copy the code

Vue allows components to be defined as factory functions that parse components on the fly. Vue triggers factory functions only when components need to be rendered, and caches the results for later re-rendering.

// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>
) :Class<Component> | void {
 
  // The second time a normal asynchronous component executes this will return factory.resolved
  if (isDef(factory.resolved)) {
    return factory.resolved
  }
  // Owner instance collection container, the reference to the same asynchronous component does not need to be resolved many times, but the instance that currently uses the asynchronous component is collected, after the asynchronous component is resolved, notify rendering update one by one
  const owner = currentRenderingInstance
  if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
    // already pending
    factory.owners.push(owner)
  }
 
  if(owner && ! isDef(factory.owners)) {// owner instance collection container
    const owners = factory.owners = [owner]
    // sync Indicates the synchronization identifier, which identifies whether it is synchronous or asynchronous
    let sync = true
    let timerLoading = null
    let timerTimeout = null
      ; (owner: any).$on('hook:destroyed'.() = > remove(owners, owner))
    const forceRender = (renderCompleted: boolean) = > {
      // Call the element's $forceUpdate() method in turn, which forces rendering once
      for (let i = 0, l = owners.length; i < l; i++) {
        (owners[i]: any).$forceUpdate()
      }
      // Remove timer after component update to clear dependencies
      if (renderCompleted) {
        owners.length = 0
        if(timerLoading ! = =null) {
          clearTimeout(timerLoading)
          timerLoading = null
        }
        if(timerTimeout ! = =null) {
          clearTimeout(timerTimeout)
          timerTimeout = null}}}// Define a resolve function. Once is a one-time wrapper that ensures that the function passed in is executed only once (to avoid multiple notification updates)
    const resolve = once((res: Object | Class<Component>) = > {
      / / the cache resolved
      factory.resolved = ensureCtor(res, baseCtor)
      // invoke callbacks only if this is not a synchronous resolve
      // (async resolves are shimmed as synchronous during SSR)
      if(! sync) { forceRender(true)}else {
        owners.length = 0}})// Define a reject function
    const reject = once(reason= >{ process.env.NODE_ENV ! = ='production' && warn(
        `Failed to resolve async component: The ${String(factory)}` +
        (reason ? `\nReason: ${reason}` : ' '))if (isDef(factory.errorComp)) {
        factory.error = true
        forceRender(true)}})// Execute the factory() function
    const res = factory(resolve, reject)
    
    sync = false
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}
Copy the code

ResolveAsyncComponent defines a resolve and reject function inside the resolveAsyncComponent, and then executes the factory() function. Factory () is the function we define in the component. Since require() is an asynchronous operation, resolveAsyncComponent returns undefined.

Back in the createComponent function, since undefined is returned, createAsyncPlaceholder is executed to create a comment node placeholder.

Resolve is the resolve function defined in resolveAsyncComponent. Resolve will save the resolved property in the factory function.

When resolveAsyncComponent does factory. Resolved, return.

Promise asynchronous components

Vue.component(
  'async-webpack-example'.// The 'import' function returns a 'Promise' object.
  () = > import('./my-async-component'))Copy the code

Webpack 2+ supports syntactic sugar for asynchronous loading: () => import(‘./my-async-component’); res = factory(resolve, reject); import(‘./my-async-component’); It’s a Promise object.

// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>
) :Class<Component> | void {

  // The second time the component executes here it will return factory.resolved
  if (isDef(factory.resolved)) {
    return factory.resolved
  }

  if(owner && ! isDef(factory.owners)) {// ...
    // Execute the factory() function,
    // Return an object containing THEN
    const res = factory(resolve, reject)
    
    if (isObject(res)) {
      if (isPromise(res)) {
        // () => Promise
        if (isUndef(factory.resolved)) {
          // If factory.resolved does not exist
          Use the then method to specify the resolve and reject callbacks
          res.then(resolve, reject)
        }
      } else if (isPromise(res.component)) {
      // ...
      }
    }
    sync = false
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}
Copy the code

Resolve is executed when the component is asynchronously loaded, reject is executed when the component is asynchronously loaded. This is a good match for webPack 2+ ‘s asynchronously loaded component (Promise).

Advanced asynchronous component

Advanced asynchronous components can define more states, such as the timeout for loading the component, components that are explicit during loading, components that are explicit when an error occurs, latency, and so on.

const AsyncComp = () = > ({
  component: import('./MyComp.vue'),
  loading: LoadingComp,
  error: ErrorComp,
  delay: 200.timeout: 3000
})
Vue.component('async-example', AsyncComp)
Copy the code

For advanced asynchronous components, it loads the same logic as the Promise () method, except for a few more properties:

// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>
) :Class<Component> | void {
  / /...
  if(owner && ! isDef(factory.owners)) {// ...
    // Execute the factory() function
    const res = factory(resolve, reject)
    if (isObject(res)) {
      if (isPromise(res)) {
        // () => Promise
        if (isUndef(factory.resolved)) {
          res.then(resolve, reject)
        }
      } else if (isPromise(res.component)) {
        // A branch of the advanced asynchronous component
        res.component.then(resolve, reject)
        if (isDef(res.error)) {
          // Failed module
          factory.errorComp = ensureCtor(res.error, baseCtor)
        }
        if (isDef(res.loading)) {
          // Set the loading module if there is one
          factory.loadingComp = ensureCtor(res.loading, baseCtor)
          if (res.delay === 0) {
            // If the wait time is 0
            factory.loading = true
          } else {
            timerLoading = setTimeout(() = > {
              timerLoading = null
              if (isUndef(factory.resolved) && isUndef(factory.error)) {
                factory.loading = true
                forceRender(false)
              }
            }, res.delay || 200)}}if (isDef(res.timeout)) {
          // The timeout period
          timerTimeout = setTimeout(() = > {
            timerTimeout = null
            if(isUndef(factory.resolved)) { reject( process.env.NODE_ENV ! = ='production'
                  ? `timeout (${res.timeout}ms)`
                  : null
              )
            }
          }, res.timeout)
        }
      }
    }
    sync = false
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}
Copy the code

Functional component

SRC /core/vdom/create-component.js -- in the createComponent method
// functional Component (functional Component)
if (isTrue(Ctor.options.functional)) {
  return createFunctionalComponent(Ctor, propsData, data, context, children) 
}
Copy the code

createFunctionalComponent

// src/core/vdom/create-functional-component.js
export function createFunctionalComponent (
  Ctor: Class<Component>,
  propsData: ?Object,
  data: VNodeData,
  contextVm: Component,
  children: ?Array<VNode>
) :VNode | Array<VNode> | void {
  / / configuration items
  const options = Ctor.options
  // props
  const props = {}
  // Props of the component itself
  const propOptions = options.props
  // Sets the props object for the functional component
  if (isDef(propOptions)) {
    // If the functional component itself provides the props option, set the props. Key value to the corresponding key value passed from the component
    for (const key in propOptions) {
      props[key] = validateProp(key, propOptions, propsData || emptyObject)
    }
  } else {
    // If the functional component does not provide props, the attribute on the component is automatically resolved to props
    if (isDef(data.attrs)) mergeProps(props, data.attrs)
    if (isDef(data.props)) mergeProps(props, data.props)
  }
  // Instantiate the rendering context of a functional component
  const renderContext = new FunctionalRenderContext(
    data,
    props,
    children,
    contextVm,
    Ctor
  )
  // Call render function to generate vNode and pass _c and render context to render function
  const vnode = options.render.call(null, renderContext._c, renderContext)
  // Add some tags to the generated VNode object to indicate that the VNode was generated by a functional component, and return the VNode
  if (vnode instanceof VNode) {
    return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
  } else if (Array.isArray(vnode)) {
    const vnodes = normalizeChildren(vnode) || []
    const res = new Array(vnodes.length)
    for (let i = 0; i < vnodes.length; i++) {
      res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext)
    }
    return res
  }
}
Copy the code

CreateFunctionalComponent in the process of creating functional components, mainly to do so a few things:

  • Set the component’spropsobject
  • Instantiate the component to get the rendering context.
  • callrenderMethod to generate a VNode.
  • Returns the marked VNode.

After all, use createElement to createElement vnodes, createComponent to createComponent vnodes, and each VNode has children. Children contains children. A VNode Tree is formed. The _render function is executed to get a VNode tree.

A link to the

Vue (V2.6.14) source code detoxification (pre) : handwritten a simple version of Vue

Vue (V2.6.14) source code detoxification (a) : preparation

Vue (V2.6.14) source code detoxification (two) : initialization and mount

Vue (V2.6.14) source code detoxification (three) : response type principle

Vue (V2.6.14) source code detoxification (four) : update strategy

Vue (v2.6.14) source code detoxification (five) : render and VNode

Vue (v2.6.14) source code: Update and patch (to be continued)

Vue (v2.6.14) source code detoxification (seven) : template compilation (to be continued)

If you think it’s ok, give it a thumbs up!! You can also visit my personal blog at www.mingme.net/