# 编译模板

# 生成对应模板

  1. 如果没有通过vue-loadervue-template-compiler进行预编译,则会改写原本的$mount生成对应的template的render函数。
  2. 生成与更新DOM通过vm._render()获取VNode,在通过vm._update() patch 获得真实的 DOM
  • 全量加载会修改 Vue.prototype.$mount 方法
// 缓存原本的 $mount 方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  // 获取对应元素的 outhtml
  el = el && query(el)
  
  // 不能是 body 和 html
  if (el === document.body || el === document.documentElement) return
  const options = this.$options
  
  // 若没有提供 render 则会通过编译生成 render
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          // { template: '#appp' }, 如果是一个id选择器,则获取钙元素的innerHtml作为模板
          template = idToTemplate(template)
        } else if (template.nodeType) {
          // template 是一个正常的元素
          template = template.innerHTML
        } else {
          return
        }
      }
    } else if (el) {
        // 设置了 el 选项, 获取 el 的 outerHtml 作为模板
        template = getOuterHTML(el)
    }
    // 模板准备就绪
    if (template) {
        const { render, staticRenderFns } = compileToFunctions(template, {
          outputSourceRange: process.env.NODE_ENNV !== 'production',
          shouldDecodeNewlines,
          shouldDecodeNewlinesForHref,
          delimiters: '{{}}', // 默认界定符 {{}}
          comments // 是否保留注释
        }, this)
        options.render = render
        options.staticRenderFns = staticRenderFns
    }
  }
  return mount.call(this, el, hydrating)
}

# mountComponent

mountComponent (原Vue.prototype.$mount) /core/instance/lifecycle

export function mountComponent (
    vm: Component,
    el?: Element,
    hydrating: Boolean
): Component {
    // 将 el 挂载到 $el 上
  vm.$el = el
  if (!vm.$options.render) return
  // 触发 beforeMount 生命周期
  callHook(vm, 'beforeMount')
  let updateComponent
  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(starTag)
      const vnode = vm._render()
      measure(`vue ${name} render`, startTag, endTag)
      
      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch` startTag, endTag)
    }
  } else {
    // mount 与 update 的方法
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }
  
  new Watcher(vm, updateCompoent, noop /* 一个空函数 */, {
  before () {
    if (vm._isMounted && !vm._isDestoryed) {
      callHook(vm, 'beforeUpdate')
    }
  }
  }, true /* 是否是 renderWatch 存放在 vm._watcher */)
  hydrating = false
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

# vm._update

  • 负责更新页面,首次渲染和更新的入口, patch 入口
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  const vm = this
  const prevEl = vm.$el
  const prevVnode = vm._vnode
  const restoreActiveInstance = setActiveInstance(vm)
  vm._vnode = vnode
  // 首次渲染
  if (!prevVnode) {
    vm.$el = vm.__patch__(vm.$el, vnode, hydrrating, false)
  } else {
    // 响应式数据更新
    vm.$el = vm.__patch__(prevVnode, vnode)
  }
  restoreActiveInstance()
  // 可以再控制面板的dom的__vue__属性看到vue实例
  if (prevEl) {
    prevEl.__vue__ = vm
  }
  if (vm.$el) {
    vm.$el.__vue__ = vm
  }
  if (vm.$vnode && vm.$parent && vm.$vnode === $vm.$parent._vnode) {
    vm.$parent.$el = vm.$el
  }
}

vm._render /src/core/instance/render

Vue.prototype._render = function(): VNode {
  const vm = this
  const { render, _parentVnode } = vm.$options
  
  if (_parentVnode) {
    vm.$scopedSlots = normalizeScopedSlots(
      _parentVnode.data.scopedSlots,
      vm.$slots,
      vm.$scopedSlots
    )
  }
  // 渲染函数可以访问占位符节点上的数据
  vm.$vnode = _parentVnode
  
  let vnode
  try {
    currentRenderingInstance = vm
    // 平时写的 render 函数 调用 _createElement(7. render Helper) 生成 Vnode 
    vnode = render.call(vm._renderProxy, vm.$createElement/* render 函数的 h */)
  } catch (e) {
    handleError(e, vm, 'render')
  } finally {
    currentRenderingInstance = null
  }
  
  if (Array.isArray(vnode) && vnode.length === 1) {
    vnode = vnode[0]
  }
  // 如果生成的不是 VNode 的实例 则创建一个空的 vnode
  if (!vnode instanceof VNode) {
    vnode = createEmptyVNode()
  }
  vnode.parent = _parentVnode
  return vnode
}