返回
Vue 实例挂载 – 内幕揭秘
前端
2023-10-29 20:01:46
迫于菜🐶 - Vue.js 源码(五)
缘起
前面四章中,我们跟随 Vue.js 技术揭秘 , 对 Vue.js 的源码部分又有了进一步的了解,今天一起来学习 Vue 实例挂载是怎么一回事。
Vue 中我们是通过 $mount
实例方法去挂载 vm 的,$mount
方法在多个文件中都有定义。因为 $mount
这个方法的对象有多种可能,比如组件、根实例、全局 API 等,这个对象的类型不一样,最后调用的函数也不一样。所以先从 $mount
这个方法开始。
$mount 方法
从其源码中可以知道,$mount
这个方法所调用的最终函数是 vm.$el && vm._isMounted ? vm._update(undefined, false) : vm.__patch__(vm._render(), hydrate)
这个函数。
而这个函数,就是我们前面提到的 Vue.js 中更新函数。Vue
的更新主要分为两种:初次渲染和后续的更新。初次渲染 ,顾名思义,就是 Vue.js 将组件首次渲染到页面上的过程,后续更新 ,则是指组件内部数据发生变化后,Vue.js 将组件重新渲染到页面上的过程。
初次渲染时,hydrate
这个参数的值为 false
,后续更新时,hydrate
这个参数的值为 true
,这就好比给 Vue.js 一个提示,告诉它这次的更新是初次渲染,还是后续更新。
初次渲染 :
vm.$el && vm._isMounted ? vm._update(undefined, false) : vm.__patch__(vm._render(), hydrate)
后续更新 :
vm.$el && vm._isMounted ? vm._update(undefined, false) : vm.__patch__(vm._render(), true)
初次渲染
初次渲染时,hydrate
这个参数的值为 false
,代表这是一个初次渲染的过程,所以会调用 Vue.prototype.__patch__
这个方法,这个方法的最终调用函数是 Vue.prototype.$forceUpdate
。
// 源码位置:packages/runtime-dom/src/component.js
Vue.prototype.__patch__ = function __patch__(
patch,
options,
hydrating = false,
removeOnly = false
) {
if (options && options.before) {
options.before.call(this)
}
const prevVNode = this._vnode
const prevVNodeNormalized = this.$vnode && this.$vnode.normalized
const ownerReactInstance = this.reactInstance
if (this._isMounted) {
// 更新的 patch
while (
patch &&
patch.componentInstance &&
!patch.componentInstance._inactive
) {
// protect inactive instance
patch = patch.componentInstance._vnode
}
if (
// 更新
this._vnode !== prevVNode ||
prevVNodeNormalized !== vnodeNormalized
) {
if (this._vnode) {
this._vnode = normalizeVNode(this._vnode, prevVNodeNormalized)
}
const prevChildren = this.$scopedSlots
? this.$scopedSlots(prevVNode)
: prevVNode.children
const parentVNode = this.$vnode && this.$vnode.parent
const defaultSlot = parentVNode && parentVNode.children[0]
const $slots = this.$slots
const $scopedSlots = this.$scopedSlots
const passedSlots = {}
let name, slot, container
if ($slots) {
for (name in $slots) {
if ($slots[name]) {
slot = $slots[name] && $slots[name].byVal
? $slots[name]
: cloneVNode($slots[name])
if (!slot.length) {
if (!defaultSlot) {
slot = createEmptyVNode()
} else if (slot === getKeepAliveChild(defaultSlot)) {
passedSlots[name] = slot
}
}
passedSlots[name] = slot
}
}
}
if ($scopedSlots) {
for (name in $scopedSlots) {
if ($scopedSlots[name]) {
slot = $scopedSlots[name] && $scopedSlots[name].byVal
? $scopedSlots[name]
: cloneVNode($scopedSlots[name])
if (!slot.length) {
if (!defaultSlot) {
slot = createEmptyVNode()
} else if (slot === getKeepAliveChild(defaultSlot)) {
passedSlots[name] = slot
}
}
container = createVNode(
'span',
{ slot: name },
slot,
false,
hydrating
)
passedSlots[name] = container.children
}
}
}
if (defaultSlot) {
// 这里其实会把当前组件的插槽与父组件的插槽做个融合,然后最终合并成一个组件的插槽传递给组件的渲染函数
// 这里的意思是,父组件没有为当前组件提供插槽,那么就用当前组件自己的插槽
// 如果有提供插槽,那么就把父组件的插槽内容放到当前组件的插槽中
passedSlots.default = defaultSlot.children
}
if (options && options.renderUpdated) {
// beforeUpdate 钩子
options.renderUpdated.call(this, prevVNode, this._vnode)
}
this.$scopedSlots = $scopedSlots
this.$slots = $slots
if (parentVNode) {
const slotContent = parentVNode.children[0]
if (this.$scopedSlots && slotContent && slotContent.hasScopedRef) {
// TODO: 有点没看懂
this.$scopedSlots = createScopedSlots(parentVNode, this.$scopedSlots)
}
}
// 更新过程
this._update(this._vnode, prevVNode, passedSlots, hydrating, removeOnly)
if (this._vnode) {
this._vnode = prevVNode
}
if (this._vnode && this._vnode.parent && this._vnode.parent._children) {
// 更新后的 vnode 如果有 parent,并且 parent 中有 _children 属性(意味着 parent 是一个组件)
// 那么就需要更新 parent 中的 _children 属性,因为 _children 属性中存储的是组件的子组件
this._updateParentListeners()
}
} else {
if (this._vnode && this._vnode.dynamicChildren) {
// 检查动态子组件,因为这些子组件在被重新渲染时可能已经改变了
const dynamicChildren = this._vnode.dynamicChildren
const oldDynamicChildren = this._prevVNode.dynamicChildren
const count = dynamicChildren.length
for (let i = 0; i < count; i++) {
const vnode = dynamicChildren[i]
const oldVnode = oldDynamicChildren[i]
if (vnode && oldVnode && vnode.ref === oldVnode.ref) {
continue
}
const parent = vnode.parent
if (parent) {
// 这里重新收集动态组件子组件的依赖
parent.refs
? parent._refs.remove(oldVnode)
: parent._children.remove(oldVnode)
this._refs
? this._refs.remove(oldVnode)
: this._children.remove(oldVnode)
adoptVNode(vnode, parent)
this._children.push(vnode)
if (vnode.dynamic) {
this._dynamicComponents[vnode.ref] = vnode
}
}
}
}
}
} else {
// 初始化渲染
// 这里会执行一些 生命周期钩子 相关的逻辑
if (this.$vnode) {
this.$vnode = patch
}
this._vnode = patch
this._vnodeNormalized = normalizeVNode(patch)
if (this.$scopedSlots) {
this._scopeId = 'data-v-' +