# 编译模板
# 生成对应模板
- 如果没有通过
vue-loader与vue-template-compiler进行预编译,则会改写原本的$mount生成对应的template的render函数。 - 生成与更新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
}