vue destroyed vs onUnmounted beforeCreate -> setup() created -> setup() beforeMount -> onBeforeMount mounted -> onMounted beforeUpdate -> onBeforeUpdate updated -> onUpdated beforeDestroy -> onBeforeUnmount destroyed -> onUnmounted activated -> onActivated deactivated -> onDeactivated errorCaptured -> onErrorCaptured
Vue3.x onUnmounted 首先之前就知道了卸载流程怎么触发的
v-if 的话, proxy set => trigger => effect => effect.scheduler() => effect.run() => componentUpdateFn => patch => unmount => 触发 unmountComponent 函数
那继续看带注释的 unmountComponent
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 const unmountComponent = ( instance: ComponentInternalInstance, parentSuspense: SuspenseBoundary | null , doRemove?: boolean ) => { if (__DEV__ && instance.type .__hmrId ) { unregisterHMR (instance) } const { bum, scope, update, subTree, um } = instance if (bum) { invokeArrayFns (bum) } if ( __COMPAT__ && isCompatEnabled (DeprecationTypes .INSTANCE_EVENT_HOOKS , instance) ) { instance.emit ('hook:beforeDestroy' ) } scope.stop () if (update) { update.active = false unmount (subTree, instance, parentSuspense, doRemove) } if (um) { queuePostRenderEffect (um, parentSuspense) } if ( __COMPAT__ && isCompatEnabled (DeprecationTypes .INSTANCE_EVENT_HOOKS , instance) ) { queuePostRenderEffect ( () => instance.emit ('hook:destroyed' ), parentSuspense ) } queuePostRenderEffect (() => { instance.isUnmounted = true }, parentSuspense) if ( __FEATURE_SUSPENSE__ && parentSuspense && parentSuspense.pendingBranch && !parentSuspense.isUnmounted && instance.asyncDep && !instance.asyncResolved && instance.suspenseId === parentSuspense.pendingId ) { parentSuspense.deps -- if (parentSuspense.deps === 0 ) { parentSuspense.resolve () } } if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { devtoolsComponentRemoved (instance) } }
这里面注释其实已经说的很清楚
scope.stop() //停止副作用函数
unmount(subTree, instance, parentSuspense, doRemove) // 调用 unmount 函数卸载组件的子树
instance.isUnmounted = true
主要就这几个,这里有一个概念 EffectScope,在 Vue2.x 中也有类似的概念
EffectScope(效果作用域)是用于管理副作用函数(effects)的工具。它提供了一种将副作用函数组织在一起并控制其执行的机制。
在 Vue 3 中,组件内部的副作用函数(如 onMounted、onUpdated、onUnmounted 等)被称为“副作用”(effects)。副作用可以是一些具有副作用的操作,例如订阅事件、发送网络请求、操作 DOM 等。
EffectScope 提供了以下功能:
创建效果作用域:使用 createScope 函数可以创建一个新的效果作用域。
开始和停止副作用函数的执行:在组件实例中,通过调用效果作用域的 run 方法可以开始执行副作用函数,调用 stop 方法可以停止执行副作用函数。
批量执行副作用函数:EffectScope 允许将多个副作用函数分组,然后一次性启动它们的执行,这样可以确保它们按照正确的顺序执行。
嵌套效果作用域:可以在一个效果作用域内创建另一个效果作用域,形成嵌套结构。嵌套的效果作用域可以独立运行,可以在父作用域停止时自动停止。
大概知道就行了,后面会专门看一看这个,简单理解就是管理组件上的副作用函数。
unmount 是递归去卸载子树,就是一个一个卸载,ref,KeepAliveContext,unmountComponent,处理SUSPENSE,TELEPORT,Fragment,动态子节点,并且直接删除 vnode。
还调用了各种钩子函数,这就是大概的卸载节点的过程,当然这里谈的主要是钩子函数,所以可能中间很多点是不会触发的。
Vue2.x destroyed 之前已经知道了大概触发流程 patch => removeNodes => Vue.prototype.$destroy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 callHook (vm, 'beforeDestroy' )vm._isBeingDestroyed = true const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options .abstract ) { remove (parent.$children , vm) } vm._scope .stop () if (vm._data .__ob__ ) { vm._data .__ob__ .vmCount -- } vm._isDestroyed = true vm.__patch__ (vm._vnode , null ) callHook (vm, 'destroyed' )
ok, 不存在什么异步,就直接触发了, 那我们来看他做了什么?
删除 parent.$childre 关于vm的引用, remove 就是一个单纯的数组方法.
1 2 3 if (parent && !parent._isBeingDestroyed && !vm.$options .abstract ) { remove (parent.$children , vm) }
停止 watch, _scope 是 EffectScope, 然后调用 watcher.teardown(), 调用 this.cleanups 清理函数, 如果 scope 在,就遍历清理。
从数据对象的观察者(__ob__)中移除对组件的引用:
1 2 3 if (vm._data .__ob__ ) { vm._data .__ob__ .vmCount -- }
调用 vm.patch 方法将当前组件的虚拟节点(_vnode)设置为 null,用于解除组件与虚拟 DOM 的关联
1 vm.__patch__ (vm._vnode , null )
总结 这里简单介绍了一下 Vue2,3 卸载组件的过程,都是类似的流程,只是方法换了,但是流程没有变。