preface

When you update a component, you need to first execute the compiler generated rendering function to get the component’s Vnode.

The rendering function can generate Vnode by means of _c, _L,, _v, _s and other methods. Such as:

  • Ordinary nodes are compiled into executable _c functions

  • The V-for node is compiled into an executable _l function

  • .

But it’s unclear how these methods work. How do they generate vNodes? All we know is that they are Vue instance methods, and today we’ll find out from the source code.

The target

How does a component generate a VNode using these runtime render helper methods

The source code interpretation

The entrance

We know that these methods are Vue instance methods, which are generally located in the/SRC /core/instance directory. In fact, I have seen Render Helper before in Vue source code interpretation (6) — instance method reading, at the end of the article.

/src/core/instance/render.js

export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  // Mount some runtime utility methods on the component instance
  installRenderHelpers(Vue.prototype)
  
  // ...
}

Copy the code

installRenderHelpers

/src/core/instance/render-helpers/index.js

/** * mount the abbreviated render utility functions on the instance, which are runtime code * these utility functions are used in the compiler generated render functions *@param {*} Target Vue instance */
export function installRenderHelpers(target: any) {
  /** * The runtime helper for the V-once directive, which marks VNode as static * is a bit redundant, since nodes with v-once instructions are treated as static nodes, so it won't go there */
  target._o = markOnce
  // Convert the value to a number
  target._n = toNumber
  /** * converts the value to a String, normal value => String(val), object => json.stringify (val) */
  target._s = toString
  /** * Render the helper function of the v-for list at runtime, iterating through the values of val, executing the render method to generate vNodes for each item in turn, and finally returning an array of VNodes */
  target._l = renderList
  target._t = renderSlot
  /** * Determine whether two values are equal */
  target._q = looseEqual
  /** * is equivalent to the indexOf method */
  target._i = looseIndexOf
  /** * Run the VNode helper that is responsible for generating a static tree, which does the following two things ** execute the staticRenderFns array rendering function, generate a static tree VNode and cache it, (isInFor must be true) * 2. Statically mark vNodes in static trees */
  target._m = renderStatic
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps
  /** * Create VNode */ for the text node
  target._v = createTextVNode
  /** * Create VNode */ for empty nodes
  target._e = createEmptyVNode
}

Copy the code

_o = markOnce

/src/core/instance/render-helpers/render-static.js

/** * Runtime helper for v-once. * Effectively it means marking the node as static with a unique key. * v-once The runtime helper for the VNode directive, adding the static flag * to the VNode is a bit redundant, since nodes with the V-once directive are treated as static nodes, so they won't go there */
export function markOnce (
  tree: VNode | Array<VNode>,
  index: number,
  key: string
) {
  markStatic(tree, `__once__${index}${key ? ` _${key}` : ` `}`.true)
  return tree
}

Copy the code

markStatic

/src/core/instance/render-helpers/render-static.js

* {isStatick: true, key: xx, isOnce: true or false} */
function markStatic (
  tree: VNode | Array<VNode>,
  key: string,
  isOnce: boolean
) {
  if (Array.isArray(tree)) {
    // tree is an array of vNodes and iterates through each VNode, statically marking each VNode
    for (let i = 0; i < tree.length; i++) {
      if (tree[i] && typeoftree[i] ! = ='string') {
        markStaticNode(tree[i], `${key}_${i}`, isOnce)
      }
    }
  } else {
    markStaticNode(tree, key, isOnce)
  }
}

Copy the code

markStaticNode

/src/core/instance/render-helpers/render-static.js

/** * tag static VNode */
function markStaticNode (node, key, isOnce) {
  node.isStatic = true
  node.key = key
  node.isOnce = isOnce
}

Copy the code

_l = renderList

/src/core/instance/render-helpers/render-list.js

/** * Runtime helper for rendering v-for lists. * Loop through val values and render vNodes for each item in turn. An array of VNodes */ is returned
export function renderList (val: any, render: ( val: any, keyOrIndex: string | number, index? : number ) => VNode): ?Array<VNode> {
  let ret: ?Array<VNode>, i, l, keys, key
  if (Array.isArray(val) || typeof val === 'string') {
    // Val is an array or string
    ret = new Array(val.length)
    for (i = 0, l = val.length; i < l; i++) {
      ret[i] = render(val[i], i)
    }
  } else if (typeof val === 'number') {
    // If val is a numeric value, iterate over all digits from 0 to val
    ret = new Array(val)
    for (i = 0; i < val; i++) {
      ret[i] = render(i + 1, i)
    }
  } else if (isObject(val)) {
    // Val is an object, traversing the object
    if (hasSymbol && val[Symbol.iterator]) {
      // Val is an iterable
      ret = []
      const iterator: Iterator<any> = val[Symbol.iterator]()
      let result = iterator.next()
      while(! result.done) { ret.push(render(result.value, ret.length)) result = iterator.next() } }else {
      // Val is a normal object
      keys = Object.keys(val)
      ret = new Array(keys.length)
      for (i = 0, l = keys.length; i < l; i++) {
        key = keys[i]
        ret[i] = render(val[key], key, i)
      }
    }
  }
  if(! isDef(ret)) { ret = [] }// Return an array of vNodes
  (ret: any)._isVList = true
  return ret
}

Copy the code

_m = renderStatic

/src/core/instance/render-helpers/render-static.js

/** * Runtime helper for rendering static trees. /** * Runtime helper for rendering static trees. Generate the VNode of the static tree and cache it, and read it directly from the cache on the next rendering (isInFor must be true) * 2, mark the VNode of the static tree with a static flag *@param { number} Index indicates the subindex * of the rendering function of the current static node in the staticRenderFns array@param { boolean} IsInFor indicates whether the current static node is wrapped inside a node containing the V-for directive */
 export function renderStatic (index: number, isInFor: boolean) :VNode | Array<VNode> {
  // Cache. When the static node is rendered a second time, the cached VNode is fetched from the cache
  const cached = this._staticTrees || (this._staticTrees = [])
  let tree = cached[index]
  // if has already-rendered static tree and not inside v-for,
  // we can reuse the same tree.
  // Return the cached VNode if the current static tree has already been rendered (i.e. cached) and is not wrapped inside the node where the V-for directive is located
  if(tree && ! isInFor) {return tree
  }
  // Execute the specified element in the staticRenderFns array (the rendering function of the static tree) to generate the VNode of the static tree and cache it
  // otherwise, render a fresh tree.
  tree = cached[index] = this.$options.staticRenderFns[index].call(
    this._renderProxy,
    null.this // for render fns generated for functional component templates
  )
  {isStatic: true, key: '__static__${index}', isOnce: false}
  markStatic(tree, `__static__${index}`.false)
  return tree
}

Copy the code

_c

/src/core/instance/render.js

/** * defines _c, which is a curlized method of createElement *@param {*} A Label name *@param {*} The JSON string * for the b property@param {*} C Array of child nodes *@param {*} D The normalized type of the node *@returns VNode or Array<VNode>
 */
vm._c = (a, b, c, d) = > createElement(vm, a, b, c, d, false)
Copy the code

createElement

/src/core/vdom/create-element.js

/** * generate vNode components or plain labels, a wrapper function, Wrapper function for providing a more flexible interface * without getting yelled at by flow */
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 the VNode of the component
  return _createElement(context, tag, data, children, normalizationType)
}

Copy the code

_createElement

/src/core/vdom/create-element.js

/** * generate vnode, * 2. The component executes createComponent to generate Vnode * 2.1. The functional component executes its render function to generate Vnode * 2.2 Ordinary components instantiate a VNode and set 4 methods on its data.hook object, which will be called in the patch phase of the component, * thus entering the instantiation and mounting phases of the child components until the rendering is completed *@param {*} Context *@param {*} The tag label *@param {*} Data property JSON string *@param {*} Children Array of child nodes@param {*} NormalizationType Indicates the normalized type of a node@returns VNode or Array<VNode>
 */
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__)) {
    // The property cannot be a reactive objectprocess.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
    )
    // If the property is a reactive object, return an empty VNode
    return createEmptyVNode()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if(! tag) {// If the tag of a dynamic component is false, return an empty VNode
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  Key, which can only be a string or a number
  // 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
      )
    }
  }

  // If there is only one function in the child node array, treat it as the default slot and clean up the child node list
  // support single function children as default scoped slot
  if (Array.isArray(children) &&
    typeof children[0= = ='function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  // Normalize the child elements
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }

   /** ** ** ** ** ** ** ** ** ** ** ** **

  let vnode, ns
  if (typeof tag === 'string') {
    // When the tag is a string, there are three possibilities for the tag:
    // 1, the platform retains the label
    // 2. Customize components
    // 3. Unknown label
    let Ctor
    // Namespace
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // The tag is a platform native tag
      // platform built-in elements
      if(process.env.NODE_ENV ! = ='production' && isDef(data) && isDef(data.nativeOn)) {
        // The.native of the V-on directive takes effect only on components
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>. `,
          context
        )
      }
      // Instantiate a VNode
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined.undefined, context
      )
    } else if((! data || ! data.pre) && isDef(Ctor = resolveAsset(context.$options,'components', tag))) {
      // The tag is a custom component
      // Find the component constructor with the specified tag name in the this.codes.ponents object
      // Create a VNode for the component. The functional component directly executes its render function to generate the VNode.
      // An ordinary component instantiates a VNode and sets four methods on its data.hook object, which will be called during the patch phase of the component.
      // This leads to instantiation, mount, and rendering of the subcomponent
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // A tag is not known, but VNode is also generated because it is possible to give a proper namespace at runtime
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined.undefined, context
      )
    }
  } else {
     // Tag is a non-string, such as a component configuration object or a component constructor
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  // Return the VNode of the component
  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

createComponent

/src/core/vdom/create-component.js

/** * create a VNode for a component, * 1, a functional component generates its VNode by executing its render method * 2, a normal component generates its VNode by executing new VNode(), An important component operation is to set four hook functions on the data.hook object, * init, prepatch, INSERT, destroy, which are called during the patch phase of the component, * such as init method, When called, the child component instance is created and mounted until rendered *@param {*} Ctor component constructor *@param {*} A JSON string * composed of the data property@param {*} Context *@param {*} Children Array of child nodes@param {*} Tag Tag name *@returns VNode or Array<VNode>
 */
export function createComponent(
  Ctor: Class<Component> | Function | Object | void, data: ? VNodeData, context: Component, children: ?Array<VNode>, tag? : string) :VNode | Array<VNode> | void {
  // The component constructor does not exist
  if (isUndef(Ctor)) {
    return
  }

  // Vue.extend
  const baseCtor = context.$options._base

  // When Ctor is a configuration object, it is converted to a constructor with vue.extend
  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }

  // If Ctor is not a function at this point, then it is an invalid component definition
  // 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
  }

  // Asynchronous components
  // async component
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      // Returns a placeholder node for the asynchronous component, which is rendered as a comment 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
      )
    }
  }

  // Attribute JSON string of the node
  data = data || {}

  // This is where the component's options are merged. This is where the compiler compiles the component into a render function, executes the render function, and then executes the _c in the render function
  // Parse the constructor options and combine the base class options to prevent global blending applied after the component constructor is created
  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor)

  // Convert the component's V-Model information (values and callbacks) 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
  // Use the attribute in the component props configuration as key and the corresponding data in the parent component as value
  // extract props
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)

  // Functional components
  // functional component
  if (isTrue(Ctor.options.functional)) {
    /** * execute the render function of the functional component to generate the VNode of the component, doing the following three things: 2. Set the render context of the functional component and pass it to the function render of the functional component. 3
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  // Get the event listener object data.on, because these listeners need to be treated as child listeners, not DOM listeners
  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  // Assign the event object with the.native modifier to data.on
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn

  if (isTrue(Ctor.options.abstract)) {
    // In the case of an abstract component, props, listeners, and slot are retained
    // abstract components do not keep anything
    // other than props & listeners & slot

    // work around flow
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }

  /** * set the hook object on the data object of the component, * add four properties to the hook object, init, prepatch, insert, destroy, * create, update, destroy, Install Component Management hooks onto the placeholder node */
  installComponentHooks(data)

  const name = Ctor.options.name || tag
  Vue-component -${cid}-${name}
  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

resolveConstructorOptions

/src/core/instance/init.js

/** * parse configuration items */ from the constructor
export function resolveConstructorOptions (Ctor: Class<Component>) {
  // Get the option from the instance constructor
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    / / cache
    const cachedSuperOptions = Ctor.superOptions
    if(superOptions ! == cachedSuperOptions) {// Indicates that the base class configuration item has changed
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      // Find the option to change
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        // Merge the changed options with the extend option
        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

/** * Parse constructor options that are subsequently modified or added */
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 ones that do not agree
  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) to attributes, values, and events on the data.attrs object callback) into * prop and event handler respectively. */
function transformModel(options, data: any) {
  // The default values are value and input
  const prop = (options.model && options.model.prop) || 'value'
  const event = (options.model && options.model.event) || 'input'
    // Store the value of the V-Model on 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 callback function corresponding to the v-model event
  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

/** * 
      
        The corresponding data in the parent component is value * When the data in the parent component is updated, it triggers a responsive update, re-executes render, generates a new VNode, and then goes here * in this way, the corresponding data in the component will be updated */
      
export function extractPropsFromVNodeData (
  data: VNodeData, // { msg: 'hello vue' }
  Ctor: Class<Component>, // The component constructortag? : string// Name of the component label
): ?Object {
  {props: {MSG: {type: String, default: xx}}}
  
  // Only the raw values are extracted. Validation and default values are handled in the 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
  if (isUndef(propOptions)) {
    // Props are not defined
    return
  }
  // Use the component props attribute as key, and the parent component passes the value as value
  // When the parent component updates, trigger a responsive update, re-execute render, generate a new vNode, and go here again
  // The corresponding data in the component will be updated
  const res = {}
  const { attrs, props } = data
  if (isDef(attrs) || isDef(props)) {
    / / traverse propsOptions
    for (const key in propOptions) {
      // Convert the key from the small hump to a hyphen
      const altKey = hyphenate(key)
      TestProps () props () props () props () props () props () props
      if(process.env.NODE_ENV ! = ='production') {
        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

/** * get res[key] = val */
function checkProp (
  res: Object,
  hash: ?Object,
  key: string,
  altKey: string,
  preserve: boolean
) :boolean {
  if (isDef(hash)) {
    // Check whether the hash (props/attrs) object contains a key or altKey
    // If it exists, 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

createFunctionalComponent

/src/core/vdom/create-functional-component.js

installRenderHelpers(FunctionalRenderContext.prototype)

/** * execute the render function of the functional component to generate the VNode of the component, doing the following three things: 2. Set the render context of the functional component and pass it to the function render of the functional component. 3@param {*} The constructor for the Ctor component *@param {*} PropsData Extra props *@param {*} A JSON string * composed of data node attributes@param {*} ContextVm Context *@param {*} Children Array of child nodes@returns Vnode or Array<VNode>
 */
export function createFunctionalComponent (
  Ctor: Class<Component>,
  propsData: ?Object,
  data: VNodeData,
  contextVm: Component,
  children: ?Array<VNode>
) :VNode | Array<VNode> | void {
  // Component configuration items
  const options = Ctor.options
  // Get props
  const props = {}
  // The props option for the component itself
  const propOptions = options.props
  // Sets the props object for a functional component
  if (isDef(propOptions)) {
    // If the function component itself provides the props option, we set the value of props. Key to the value of the corresponding key passed on the component
    for (const key in propOptions) {
      props[key] = validateProp(key, propOptions, propsData || emptyObject)
    }
  } else {
    // If the current functional component does not provide the props option, 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 for the functional component
  const renderContext = new FunctionalRenderContext(
    data,
    props,
    children,
    contextVm,
    Ctor
  )

  // Call the render function, generate vNode, and pass _c and render context to the render function
  const vnode = options.render.call(null, renderContext._c, renderContext)

  // Return VNode by adding some tags to the resulting VNode object to indicate that the VNode was generated by a functional component
  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

installComponentHooks

/src/core/vdom/create-component.js

const hooksToMerge = Object.keys(componentVNodeHooks)
/** * set the hook object on the data object of the component. * Add four properties to the hook object, init, prepatch, INSERT, destroy, * responsible for creating, updating, destroying the component */
 function installComponentHooks(data: VNodeData) {
  const hooks = data.hook || (data.hook = {})
  // Iterate through the hooksToMerge array, hooksToMerge = ['init', 'prepatch', 'insert' 'destroy']
  for (let i = 0; i < hooksToMerge.length; i++) {
    For example, key = init
    const key = hooksToMerge[i]
    // Get the method corresponding to key from data.hook object
    const existing = hooks[key]
    // componentVNodeHooks method for key objects
    const toMerge = componentVNodeHooks[key]
    // Merge the hook method passed by the user with the hook method provided by the framework
    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

// Call the inline hook on the component Vnode during patch
// inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
  / / initialization
  init(vnode: VNodeWithData, hydrating: boolean): ? 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
      )
      / / execute component $mount method, enter the mount the stage, the next is the render function is obtained by the compiler, go on mount, patch this road, until the component rendering a page
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },

  // Update the VNode to update the properties on the old VNode with the new VNode configuration
  prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    // Component configuration items of the new VNode
    const options = vnode.componentOptions
    // The component instance of the old VNode
    const child = vnode.componentInstance = oldVnode.componentInstance
    // Update the properties on the child with the properties on the vNode
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children)},// Execute the component's Mounted declaration for periodic hooks
  insert(vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    // If the component is not mounted, call Mounted to declare the periodic hook
    if(! componentInstance._isMounted) { componentInstance._isMounted =true
      callHook(componentInstance, 'mounted')}// Handle exceptions for keep-Alive components
    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 */)}}},If the component is not keep-alive, the component's state is cached by deactivating the component without destroying the instance. If the component is not keep-alive, the component's state is cached by calling the instance's $destroy method */
  destroy (vnode: MountedComponentVNode) {
    // Obtain the component instance from the vnode
    const { componentInstance } = vnode
    if(! componentInstance._isDestroyed) {// If the component instance was not destroyed
      if(! vnode.data.keepAlive) {// If the component is not wrapped by a keep-alive component, call $destroy directly to destroy the component
        componentInstance.$destroy()
      } else {
        // 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

/ * * * 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 = {
    _isComponent: true._parentVnode: vnode,
    parent
  }
  // Check the inline template render 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

conclusion

Interviewer: How does a component become a VNode?

A:

  • The component instance is initialized, and then $mount is executed to enter the mount phase

  • If you include only the run-time vue.js, you only go straight to the mount phase, because the component is now a rendering function, and the compilation is done via the module packager + vue-loader + vue-template-compiler

  • If you are not using precompilation, you must use full vue.js

  • If the render option is not found on the component configuration item during the mount, the compile phase is entered

  • Compile the template string into an AST syntax tree, which is actually an ordinary JS object

  • Then optimize the AST, traverse the AST object, and mark whether each node is static static; The static root nodes are then further flagged, and updates to these static nodes are skipped on subsequent component updates to improve performance

  • Next, generate the render function from the AST. The generated render function consists of two parts:

    • Responsible for generating the dynamic node VNode render function

    • There is also an array of staticRenderFns, where each element is a function that generates a static VNode. These functions will be part of the render function that generates a VNode with a static node

  • Next, place the render function on the component’s configuration object and enter the mount phase by executing the mountComponent method

  • The final function responsible for rendering and updating the component is a method called updateComponent. This method first executes the vm._render function, which executes the compiler generated render to get the VNode of the component

  • The actual work of generating a component into a VNode is done by the _c, _o, _l, _m methods in the render function, which are mounted to the Vue instance and are responsible for generating the component VNode at run time

VNode is a JS object that represents a component template. It is a common JS object that describes the information of each node in the component

Set the component configuration information, and then use new VNode(component information) to generate the component VNode

  • _c, the VNode responsible for generating components or HTML elements, _c is the most complex and central method of all render Helper methods, and the rest of _xx is part of it

    • Receive a label, a property JSON string, an array of child nodes, and a node normalization type as parameters

    • If the label is a platform-reserved label or an unknown element, then new VNode(label information) gets VNode

    • If the label is a component, the createComponent method is executed to generate the VNode

      • A functional component executes its own render function to generate a VNode

      • Ordinary components instantiate a VNode and set 4 methods on the data.hook object, which will be called in the patch phase of the component, thus entering the instantiation and mounting phases of the child components, and then compile and generate the rendering function until the rendering is completed

      • Of course, the VNode will be generated before some configuration processing such as:

        • Subcomponent options merge, merging global configuration items into component configuration items

        • V-model for handling custom components

        • PropsData (props); propsData (props); propsData (props); This happens again when a new VNode is generated when the component is updated, and this is how the props response works

        • Processing other data, such as listeners

        • Install built-in init, prepatch, INSERT, and destroy hooks to data.hooks objects that are used in the patch phase of the component

  • _l, the runtime helper function for rendering the V-for list, iterates through the values of val, executes the render method to generate vNodes for each item in turn, and finally returns an array of VNodes

  • _m, the VNode responsible for generating static nodes, that is, executing the function that sets the index in the staticRenderFns array

A simple summary of the function of the Render Helper is to mount some runtime utility methods on the Vue instance that are used in the compiler generated render function to generate the VNode of the component.

All that remains is the patch phase. The next article will show you how to render the VNode of a component to the page.

Form a complete set of video

(11) — Render helper

Please focus on

Welcome everyone to follow my gold mining account and B station, if the content has to help you, welcome everyone to like, collect + attention

link

  • Vue source code interpretation (1) – preface

  • Vue source code interpretation (2) — Vue initialization process

  • Vue source code interpretation (3) – response principle

  • Vue source code interpretation (4) — asynchronous update

  • Vue source code Interpretation (5) — Global API

  • Vue source code interpretation (6) — instance method

  • (7) — Hook Event

  • Vue source code interpretation (8) — compiler parsing

  • Vue source code interpretation (9) — compiler optimization

  • Vue source code interpretation (10) — compiler generation rendering function

  • (11) — Render helper

  • Vue source code interpretation (12) — patch

Learning exchange group

link