The sixth part of VUE source code learning-what has the new Vue done (mounted)

1. Overview

  If the previous parts are all preparations, then from the beginning of the mount, we will enter the formal rendering work until the page is rendered. Before we start analyzing the code, we can think about what we should do.

1. Replace the variables and expressions in the template with actual values. such as:

 <div id="app">
   <ul>
    <li v-for="item in items">
      {{item.id}}
    </li>
  </ul>
<div>

..
var vm = new Vue({
    el:"#app",
    data:{
       items:[
        {id:1},
        {id:2},
        {id:3}
      ]
    }
})

After replacement, the actual dom is formed and rendered

<div id="app">
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>
<div>

2. Establish an update mechanism. When the data is updated, the DOM is also re-rendered. For example, after the items array is updated, the dome is also updated accordingly.

In fact, vue is also around these two points, the following is a schematic diagram of the process of mounting.

(1) HTML string extraction, whether it is el or template, finally extract the HTML string.

(2) Compile, compile html string into AST abstract number, and then splice it into render expression.

(3) To form a real dom, render is converted to vnode, and finally the actual dom is generated, and updated monitoring is added.

Below we will analyze from the source code, this part involves more content and knowledge points, some content will be put back as a special chapter to introduce.

Second, the $ mount at compile time

    The $ mount definition in Vue appears in two files, namely src / platform / web / entry-runtime-with-compiler.js and src / platform / web / runtime / index.js. In the previous file, the normal The template string is converted into render syntax, and then the mount in the next file is called to complete the rendering of the dom. That is, the compilation process is implemented in entry-runtime-with-compiler.js. If we use the render syntax, we only need to call the mount at runtime, which can save compilation time. But in actual development, vue is recommended to use the template mode, after all, the readability will be better.

Let's take a look at the $ mount of entry-runtime-with-compiler.js

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  //根据id获取dom元素
  el = el && query(el)

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  //1、根据template/el,获取html的字符串
  if (!options.render) {
    //获取template
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
     //2、编译,生成render
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  //3、调用runtime的mount
  return mount.call(this, el, hydrating)
}

This method is mainly realized

1. According to el or template, get the html string, such as the above example, the obtained content:

"<div id="app">
  <ul>
    <li v-for="item in items">
      {{item.id}}
    </li>
  </ul>
</div>"

2. Convert the string into a render function expression. We also introduced the expression of render before. Students familiar with react should be familiar with this. This process involves compilation, we have a dedicated chapter to analyze. The render function after the final conversion:

f() {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('ul',_l((items),function(item){return _c('li',[_v("\n      "+_s(item.id)+"\n    ")])}))])}
}

3. Call the mount definition in runtime / index.js and continue the subsequent process.

4. Mount at runtime

After the template (or the external HTML pointed to by el) is compiled and converted into a render function, the runtime moun is responsible for the real mounting process. It converts the render expression into vnode, and finally creates a real dom node.

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

   There are two elinput parameters for mount. The first one is  that it represents the mounted element, which can be a string or a DOM object. If the string is a string, it will call a  query method to convert it to a DOM object. The second parameter is related to server-side rendering. In the browser environment, we do not need to pass the second parameter.

The mountComponent method was returned.

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  //1、挂载前,执行beforMount
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    //2、定义updateComponent,vm._render将render表达式转化为vnode,vm._update将vnode渲染成实际的dom节点
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  //3、首次渲染,并监听数据变化,并实现dom的更新
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  //4、挂载完成,回调mount函数
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

The complete mounting process is shown here.

1. Execute the beforeMount callback method. At this point, el has been assigned to vm. $ El. Taking the example of VUE source code learning part 3-What did new Vue do (overview) as an example, el is already worthwhile.

 beforeMount(){
    console.log("beforeMount");
     console.log("this el:"+this.$el);//[object HTMLDivElement]
     console.log("this data:"+this.$data);//[object Object]
     console.log("this msg:"+this.msg);//tttt
   }

2. Define the updateComponent method, vm._render, to convert the render expression into a vnode, and vm._update to render the vnode into the actual dom.

3. Build watcher instance, it has two functions,

(1) Execute the updateComponent method to realize the rendering of dom, and complete the expression's dependence collection on attribute variables.

  (2) Once the attribute variable in the included expression changes, update will be executed again.

Regarding the watcher part, it will be analyzed in the subsequent responsive principle, and only the current stage will be understood.

4. Determine whether it is the root node, and execute the callback method of mounting completion.

Next, we focus on the render and update process.

Five, vm._render

This method is to convert the render expression to vnode. First, let's see what is a vnode. The vnode is located in core / vdom / vnode.js and is defined as follows:

export default class VNode {
  tag: string | 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 nodes
  fnOptions: ?ComponentOptions; // for SSR caching
  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
    /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
    this.data = data
    /*当前节点的子节点,是一个数组*/
    this.children = children
    /*当前节点的文本*/
    this.text = text
     /*当前虚拟节点对应的真实dom节点*/
    this.elm = elm
    /*当前节点的名字空间*/
    this.ns = undefined
    /*编译作用域*/
    this.context = context
    /*函数化组件作用域*/
    this.fnContext = undefined
    this.fnOptions = undefined
    this.fnScopeId = undefined
    /*节点的key属性,被当作节点的标志,用以优化*/
    this.key = data && data.key
    /*组件的option选项*/
    this.componentOptions = componentOptions
    /*当前节点对应的组件的实例*/
    this.componentInstance = undefined
     /*当前节点的父节点*/
    this.parent = undefined
    /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
    this.raw = false
    /*静态节点标志*/
    this.isStatic = false
    /*是否作为跟节点插入*/
    this.isRootInsert = true
    /*是否为注释节点*/
    this.isComment = false
    /*是否为克隆节点*/
    this.isCloned = false
    /*是否有v-once指令*/
    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
  }
}

A vnode is a collection of dom attributes, which can be considered a simplified dom.

vm._render is located in src / core / instance / render.js,

  Vue.prototype._render = function (): VNode {
   ...
    try {
      //核心代码,生成vnode
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      ...
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }

Let's look directly at the core code. Vm._renderProxy is the execution context. As we mentioned earlier, it is vm in the production environment. If you don't know the call function, translate it to vm.render (vm. $ CreateElement), which may be easier to understand .

See the render function in this example, calling vm._c to create vnodes at all levels, so where is this function defined, in the initRender method mentioned in the previous chapter

//定义._c方法,对template进行render处理的方法
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)

createElement is located in src / core / vdom / create-element.js

export function createElement (
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
): VNode | Array<VNode> {
   //处理data的入参,当没有data属性的情况下,这个参数表示的是children
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  return _createElement(context, tag, data, children, normalizationType)
}

We see that createElement is an encapsulation of _createElement and handles different input parameter writing methods. Continue to look at the code of _createElement

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__)) {
    process.env.NODE_ENV !== 'production' && warn(
      `Avoid using observed data object as vnode data: ${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) {
    // 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
  if (Array.isArray(children) &&
    typeof children[0] === 'function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }

  //1、规整childern子节点
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  //2、创建vnode
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // 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
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // 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()
  }
}

    There are 5 input parameters, context represents the context, that is, the current component object, tag represents the label of the node, data represents the data data of the component, VNode type, children represents the current child node, normalizationType represents the standardized type of child node The render method needs to be organized.

  This method mainly includes two functions, regularizing children, and creating vnode.

1. Children regular

// 1. When the children contains components - because a functional component
// may return an Array instead of a single root. In this case, just a simple
// normalization is needed - if any child is an Array, we flatten the whole
// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep
// because functional components already normalize their own children.
export function simpleNormalizeChildren (children: any) {
  for (let i = 0; i < children.length; i++) {
    if (Array.isArray(children[i])) {
      return Array.prototype.concat.apply([], children)
    }
  }
  return children
}

// 2. When the children contains constructs that always generated nested Arrays,
// e.g. <template>, <slot>, v-for, or when the children is provided by user
// with hand-written render functions / JSX. In such cases a full normalization
// is needed to cater to all possible types of children values.
export function normalizeChildren (children: any): ?Array<VNode> {
  return isPrimitive(children)
    ? [createTextVNode(children)]
    : Array.isArray(children)
      ? normalizeArrayChildren(children)
      : undefined
}

simpleNormalizeChildren refers to the processing of functional components. Functional components return an array instead of a root node. You need to flatten the array through Array.prototype.concat so that it is only one layer deep.

NormalizeChildren handles the hand-written render function or JSX. When children is the basic type, a Textvnode node is created by createTextVNode; when a nested array is generated when compiling v-for and slot, normalizeArrayChildren is called.

After children's regulation, children become an Array of type VNode.

2. Create a vnode

First determine whether the tag is of type String, and then determine whether it is a built-in tag (html or svg tag, such as div, body, etc.), if it is, create a normal VNode, initialize tag, data, children and other variables.

If it is a registered component, call createComponent to create a VNode, otherwise create a VNode with an unknown label. If it is not a String type, call createComponent to create a component type VNode. We will talk about the component creation process later.

In short, after createElement is created, the vnode tree is created, which is the virtual DOM.

六、vm._update

vm._update will render the vnode into a real dom node. There are two places when it is called, the first is the first rendering, and the second is the data update. In this chapter, we only focus on the first case. We will update the data in a special chapter for analysis. vm._update is defined in src / core / instance / lifecycle.js.

 Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    ...
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    ...
  }

Let's take a look at the core code and call vm .__ patch__ for the first rendering. The web platform is defined in src / platforms / web / runtime / index.js

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

If it is a browser environment, it is src / platforms / web / runtime / patch.js (not required for server-side rendering, it is an empty function).

export const patch: Function = createPatchFunction({ nodeOps, modules })

createPatchFunction returns the patch function, defined in src / core / vdom / patch.js.

export function createPatchFunction (backend) {
...
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
      //是否是实际的元素
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
      } else {
        if (isRealElement) {
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (process.env.NODE_ENV !== 'production') {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '<p>, or missing <tbody>. Bailing hydration and performing ' +
                'full client-side render.'
              )
            }
          }
          // either not server-rendered, or hydration failed.
          // create an empty node and replace it
          //转成vnode
          oldVnode = emptyNodeAt(oldVnode)
        }

        // replacing existing element
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)

        // create new node
        //核心方法,根据vnode渲染成实际的dom
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )

        // update parent placeholder node element, recursively
        if (isDef(vnode.parent)) {
          let ancestor = vnode.parent
          const patchable = isPatchable(vnode)
          while (ancestor) {
            for (let i = 0; i < cbs.destroy.length; ++i) {
              cbs.destroy[i](ancestor)
            }
            ancestor.elm = vnode.elm
            if (patchable) {
              for (let i = 0; i < cbs.create.length; ++i) {
                cbs.create[i](emptyNode, ancestor)
              }
              // #6513
              // invoke insert hooks that may have been merged by create hooks.
              // e.g. for directives that uses the "inserted" hook.
              const insert = ancestor.data.hook.insert
              if (insert.merged) {
                // start at index 1 to avoid re-invoking component mounted hook
                for (let i = 1; i < insert.fns.length; i++) {
                  insert.fns[i]()
                }
              }
            } else {
              registerRef(ancestor)
            }
            ancestor = ancestor.parent
          }
        }

        // destroy old node
        if (isDef(parentElm)) {
          removeVnodes(parentElm, [oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
...
}

CreatePatchFunction defines a series of methods internally, and finally returns the patch method. It has four parameters, which are actually passed in vm .__ patch __ (vm. $ el, vnode, hydrating, false) We also take the opening example as an example, vm. $ El is the dom with the id of app, vnode is the virtual object after rendering, hydrating indicates whether it is server-side rendering, and the last parameter removeOnly is for  transition-group use.

 oldVnode is the actual dom whose id is app, so isRealElement is true, and emptyNodeAt converts oldVnode into Vnode object. Next call createElm to create a real dom, let's focus on this method.

 function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
    if (isDef(vnode.elm) && isDef(ownerArray)) {
      // This vnode was used in a previous render!
      // now it's used as a new node, overwriting its elm would cause
      // potential patch errors down the road when it's used as an insertion
      // reference node. Instead, we clone the node on-demand before creating
      // associated DOM element for it.
      vnode = ownerArray[index] = cloneVNode(vnode)
    }

    vnode.isRootInsert = !nested // for transition enter check
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }

    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    //1、合法性校验
    if (isDef(tag)) {
      if (process.env.NODE_ENV !== 'production') {
        if (data && data.pre) {
          creatingElmInVPre++
        }
        if (isUnknownElement(vnode, creatingElmInVPre)) {
          warn(
            'Unknown custom element: <' + tag + '> - did you ' +
            'register the component correctly? For recursive components, ' +
            'make sure to provide the "name" option.',
            vnode.context
          )
        }
      }
      //2、创建该vnode的dom元素
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
      setScope(vnode)

      /* istanbul ignore if */
      if (__WEEX__) {
        // in Weex, the default insertion order is parent-first.
        // List items can be optimized to use children-first insertion
        // with append="tree".
        const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
        if (!appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
        createChildren(vnode, children, insertedVnodeQueue)
        if (appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
      } else {
        //3、创建子节点
        createChildren(vnode, children, insertedVnodeQueue)
        //4、执行所有的create钩子
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        //5、插入节点
        insert(parentElm, vnode.elm, refElm)
      }

      if (process.env.NODE_ENV !== 'production' && data && data.pre) {
        creatingElmInVPre--
      }
    } else if (isTrue(vnode.isComment)) {
      vnode.elm = nodeOps.createComment(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    } else {
      vnode.elm = nodeOps.createTextNode(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    }
  }

First, verify the validity of the tag, and create the element through nodeOps.createElement. Let's take a look at this method, located at src / plateforms / web / runtime / node-ops.js

export function createElement (tagName: string, vnode: VNode): Element {
  //核心方法
  const elm = document.createElement(tagName)
  if (tagName !== 'select') {
    return elm
  }
  // false or null will remove the attribute but undefined will not
  if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {
    elm.setAttribute('multiple', 'multiple')
  }
  return elm
}

In the end, the native method document.createElement is called, and setAttribute implements the creation of the dom node.

Next, call the createChildren method to create the child node element.

function createChildren (vnode, children, insertedVnodeQueue) {
    //如果有子节点,则调用creatElm递归创建
    if (Array.isArray(children)) {
      if (process.env.NODE_ENV !== 'production') {
        checkDuplicateKeys(children)
      }
      for (let i = 0; i < children.length; ++i) {
        createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
      }
    } else if (isPrimitive(vnode.text)) {//对于叶节点,直接添加text
      nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
    }
  }

If there is a child node, it is created recursively by calling creatElm, and for the leaf node, it is directly added to the element.

After the elements of the child node are created, call the  invokeCreateHooks method to execute all the hooks of create and  vnode push them into  insertedVnodeQueue it.

Finally call insert to insert the dom into the parent element. Because it is a recursive call, the child element must be inserted before the parent element. During the insert process, the native appendChild is called to insert the element.

For our example, the order of creation and insertion is li-> ul-> div, and finally the completed DOM is inserted into the body.

At this point, after the first rendering is completed, we see that in the end, the original methods are used, such as createElement, setAttribute, appendChild, etc. to achieve the creation of the node.

7. Summary

Through various init and mount, our new Vue is also over, and the original data and template are finally rendered into the actual DOM. The following is an overview of the process summarized by the predecessors, I think it is very good, simple and clear.

Throughout the analysis process, we have neglected as many details as possible, so that everyone can have a clear understanding of the main line. Then in the next chapter, we need to learn this knowledge.

Published 33 original articles · Liked 95 · Visitors 30,000+

Guess you like

Origin blog.csdn.net/tcy83/article/details/86566589