讲到虚拟Dom,那么我们先从初始化渲染开始。
src/core/instance/render.js
初始化相关属性
// 定义并导出initRender方法,参数为vm(组件)
export function initRender (vm: Component) {
// 初始化根实例的虚拟节点
vm._vnode = null // the root of the child tree
// 定义实例的静态树节点
vm._staticTrees = null // v-once cached trees
// 获取vm的$options配置选项,并赋值给options
const options = vm.$options
//设置父占位符节点
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
// 设置renderContext存储父节点有无上下文
const renderContext = parentVnode && parentVnode.context
// 将子虚拟节点转换成格式化的对象结构存储在实例的$slot中
vm.$slots = resolveSlots(options._renderChildren, renderContext)
//初始化$scopedSlots并设置为空对象
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
// 绑定createElement函数到这个实例
// 内部实际调用createElement函数,并且适当的渲染上下文
// 参照的顺序是:tag, data, children, normalizationType, alwaysNormalize
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
// 规范化始终应用于公共版本,用于用户编写的渲染函数。
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
// attrs和$listeners被暴露了,为了更容易创建高阶组件
// 他们需要做响应化这样高阶组件就能够保持实时的更新
const parentData = parentVnode && parentVnode.data
// 对事件和属性监听器进行响应式处理,建立观察状态
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
}
上面的方法主要就是初始化相关的属性,对事件和属性做响应化处理
挂载方法初始化返回虚拟节点
主要是给Vue原型对象挂载$nextTick和_render方法,render方法里面主要是调用createElement方法
// 定义并导出renderMixin
export function renderMixin (Vue: Class<Component>) {
// 为Vue原型对象绑定运行时相关的辅助方法
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
// 挂载Vue原型对象$nextTick,参数为函数
Vue.prototype.$nextTick = function (fn: Function) {
// 返回xtTick函数的执行结果
return nextTick(fn, this)
}
// 挂载Vue原型对象_render
Vue.prototype._render = function (): VNode {
//将实例赋值给vm变量
const vm: Component = this
// 从$options对象中获取render方法和 _parentVnode对象
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.
// 设置父节点虚拟节点,允许渲染函数具有访问权限到占位符节点上的数据
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.
// 不需要维护堆栈,因为所有的render函数都是被分开的。当父组件被打补丁时,被叫做嵌套组件render函数。
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
// 返回错误的渲染结果,或者以前的虚拟节点去阻止渲染错误造成空组件
/* istanbul ignore else */
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 the returned array contains only a single node, allow it
// 如果返回的数组只包含一个节点,允许他这么操作
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
// 如果呈现渲染函数出错,就返回一个空节点
if (!(vnode instanceof 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
}
}
我们从组建挂载开始来看虚拟dom在vue/src/core/instance/lifecycle.js中的mountComponent方法,里面最主要的是有一个更新组件的方法
updateComponent = () => {
// 实际调用是在lifeCycleMixin中定义的_update和renderMixin中定义的_render
vm._update(vm._render(), hydrating)
}
// Vue原型对象挂载_update的方法,传入虚拟节点,和hydrating
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
// Vue原型的__patch__注入入口,基于使用的渲染后端
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
// 如果父节点是高阶组件,也像$el一样更新他
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
//在调度程序中更新钩子被调用,以确保子级在父级的更新钩子中的得以更新
}
最后就是在各个平台进行__patch__
platforms/web/runtime/index.js
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop