vue updated vs onUpdated beforeCreate -> setup() created -> setup() beforeMount -> onBeforeMount mounted -> onMounted beforeUpdate -> onBeforeUpdate updated -> onUpdated beforeDestroy -> onBeforeUnmount destroyed -> onUnmounted activated -> onActivated deactivated -> onDeactivated errorCaptured -> onErrorCaptured
vue3.x onUpdate 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 if (!instance.isMounted ) { } else { if (bu) { invokeArrayFns (bu) } patch ( prevTree, nextTree, hostParentNode (prevTree.el !)!, getNextHostNode (prevTree), instance, parentSuspense, isSVG ) if (u) { queuePostRenderEffect (u, parentSuspense) } if (!isAsyncWrapper (vnode) && (vnodeHook = next.props && next.props .onVnodeUpdated )) { queuePostRenderEffect ( () => invokeVNodeHook (vnodeHook!, parent, next!, vnode), parentSuspense ) } if (__COMPAT__ && isCompatEnabled (DeprecationTypes .INSTANCE_EVENT_HOOKS , instance)) { queuePostRenderEffect ( () => instance.emit ('hook:updated' ), parentSuspense ) } }
这里是再 patch 更新组件完成,然后调用 beforeUpdate, 这就是钩子插入的过程。
调用就要看 ref or reactive,会在 set 的时候 triggerRefValue(this, newVal); 然后 triggerEffects, 然后找到effect2.scheduler();
1 2 3 4 5 const effect = (instance.effect = new ReactiveEffect ( componentUpdateFn, () => queueJob (update), instance.scope ))
ok,现在可以来说一说基本的调用过程了,这是基本的创建
1 2 3 4 5 6 7 const effect = (instance.effect = new ReactiveEffect ( componentUpdateFn, () => queueJob (update), instance.scope )) const update : SchedulerJob = (instance.update = () => effect.run ())
在写入属性的时候触发 proxy 的时候,触发 set
1 triggerRefValue(this, newVal);
需要知道 ref, reactive 在创建的时候是创建的 dep,用于记录关联,所以触发的时候 this.dep 可以调用找到当前的 reactvice.
然后就可以调用 triggerEffect, 触发 effect.scheduler(), 也就是 queueJob(update).
这里里面会触发调度任务,会把 update 触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 export function queueJob (job: SchedulerJob ) { if ( !queue.length || !queue.includes ( job, isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex ) ) { if (job.id == null ) { queue.push (job) } else { queue.splice (findInsertionIndex (job.id ), 0 , job) } queueFlush () } } function queueFlush ( ) { if (!isFlushing && !isFlushPending) { isFlushPending = true currentFlushPromise = resolvedPromise.then (flushJobs) } }
然后调用 flushJobs, 里面会不停的循环 queue, 执行里面的 function。
在全部执行完成以后执行 flushPostFlushCbs(seen)。
于是就触发刚才插入的钩子函数
1 2 3 if (u) { queuePostRenderEffect (u, parentSuspense) }
vue2.x updated 这里需要大概知道 Vue2 的一些核心概念, wathcer & dep,也就是 Vue2.x 更新的核心概念
watcher , dep 这两篇文章是之前我看 Vue2.x 的源码的时候写的,包含代码注释。
可以简单理解为是一个发布-订阅的关系,在之前看代码中,我们知道有在创建组件的函数中 mountComponent 当中,会新建 wathcer.
在组件的创建完成以后,你访问属性,会通过 Object.defineProperty 劫持,然后创建 Dep。通过下面代码
1 2 3 4 5 6 7 8 9 10 addDep (dep: Dep ) { const id = dep.id if (!this .newDepIds .has (id)) { this .newDepIds .add (id) this .newDeps .push (dep) if (!this .depIds .has (id)) { dep.addSub (this ) } } }
和 watcher 建立联系。
如果属性发生变化,通过 Object.defineProperty 劫持发现,然后通知 dep,然后通知 watcher,watcher.update().
为什么要知道这个? 因为这个钩子就是通过 watcher.update() 触发的,总得简单知道他是怎么触发的吧。
1 2 3 4 5 6 7 8 9 10 update ( ) { if (this .lazy ) { this .dirty = true } else if (this .sync ) { this .run () } else { queueWatcher (this ) } }
懒加载就是下次获取的更新,一个同步,一个异步(队列)。
queueWatcher 中往 queue.push(watcher), 然后调用 flushSchedulerQueue。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 for (index = 0 ; index < queue.length ; index++) { watcher = queue[index] if (watcher.before ) { watcher.before () } id = watcher.id has[id] = null watcher.run () if (__DEV__ && has[id] != null ) { circular[id] = (circular[id] || 0 ) + 1 if (circular[id] > MAX_UPDATE_COUNT ) { warn ( 'You may have an infinite update loop ' + (watcher.user ? `in watcher with expression "${watcher.expression} "` : `in a component render function.` ), watcher.vm ) break } } } const activatedQueue = activatedChildren.slice ()const updatedQueue = queue.slice ()resetSchedulerState ()callActivatedHooks (activatedQueue)callUpdatedHooks (updatedQueue) cleanupDeps ()
这个时候就可以开始说了
1 2 3 4 5 6 7 8 9 for (queue..) { if (watcher.before ) { watcher.before () } watcher.run () } callActivatedHooks (activatedQueue) callUpdatedHooks (updatedQueue)
这个就是执行的过程,
总结 对比 Vue2 和 Vue3 的实现,其实本质上还是两个版本核心实现的差别
都是通过 mountComponent 实现创建更新的函数,不过一个是 watcher 一个是 effect. dep 功能有差异。
然后都是通过 set 的时候触发, 这里区别又来了。
Vue2.x set 通过创建的 dep ,通知对应的 watcher, 然后调用 watcher.update(), 在触发 flushSchedulerQueue。
Vue3.x 也是通过 set 触发 triggerRefValue(this, newVal), 然后找到 effect.scheduler(), 执行 update 函数, 更新函数,更新完成以后调用生命周期