password
Created
Feb 19, 2023 03:45 PM
type
Post
status
Published
date
Oct 20, 2021
slug
summary
Vue中组件的生命周期实现机制
tags
Vue源码
Vue
category
源码
icon
Vue2中的生命周期
在组件对象中进行定义
export default { created() { // ... }, mounted() { // ... }, beforeDestory() { // ... } }
Vue3中的生命周期
Vue3中支持通过hook的方式, 在setup函数中进行生命周期定义, 也就意味着我们可以对生命周期方法进行更加细粒化的抽象.
同时, setup函数替代了
beforeCreate
和created
函数, 这也使得我们可以在setup函数中进行一些初始化的工作. 如: 进行异步请求等与Vue2相比, 主要的生命周期变化
Vue2周期
Vue3周期
use setup()
use setup()
onBeforeMont
onBeforeUpdate
onUpdated
onBeforeDestory
onUnmounted
onActivated
onDeactivated
onErrorCaptured
除此外, Vue3还提供了额外的两个声明周期方法
onRenderTracked
onRenderTriggered
生命周期的实现机制
除了
onErrorCaptured
外, 其他的生命周期均通过createHook
来进行创建const onBeforeMount = createHook('bm') const onMounted = createHook('m') const onBeforeUpdate = createHook('bu') const onUpdated = createHook('u') const onBeforeUnmount = createHook('bum') const onUnmounted = createHook('um') const onRenderTriggered = createHook('rtg') const onRenderTracked = createHook('rtc') const onErrorCaptured = (hook, target = currentInstance) => { injectHook('ec', hook, target) }
createHook
会返回一个函数, 内部通过injectHook
来进行注册, 相当于是一次函数柯里化的实现, 避免了传入过多相同的参数const createHook = function(lifecycle) { return function(hook, target = currentInstance) { injectHook(lifecycle, hook, target) } }
injectHook
则是将注册的hook保存在一个数组中, 数组则保存在当前组件实例中.function injectHook(type, hook, target = currentInstance, prepend = false) { const hooks = target[type] || (target[type] = []) // 封装hook勾子函数, 并缓存 const wrappedHook = hook.__weh || ( hook.__weh = (...args) => { if (target.isUnmounted) { return } // 停止依赖收集 pauseTracking() // 设置target为当前运行的组件实例 setCurrentInstance(target) // 执行勾子函数 res = callWithAsyncErrorHandling(hook, target, type, args) // 重置当前运行的组件实例 setCurrentInstance(null) // 恢复依赖收集 resetTracking() return res } ) if (prepend) { hooks.unshift(wrappedHook) } else { hooks.push(wrappedHook) } }
具体的组件生命周期执行时机
组件声明周期的执行本质上和
hook
的意义是一致的, 在不同的周期下执行对应的生命周期勾子函数的数组.Mount相关
Mount
相关生命周期在setupRenderEffect
中进行挂载和执行, 主要有两个声明周期onBeforeMount
: 在组件挂载之前执行 onMounted
: 在组件挂载完毕之后执行const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => { // 创建响应式的副作用渲染函数 instance.update = effect(function componentEffect() { if (!instance.isMounted) { // 获取组件实例上通过onBeforeMount勾子函数和onMounted注册的勾子函数 const { bm, m } = instance; // 渲染组件生成子树vnode const subTree = (instance.subTree = renderComponentRoot(instance)) // 执行beforeMount勾子函数 if (bm) { invokeArrayFns(bm) } // 将子树vnode挂载到container中 patch(null, subTree, container, anchor, instance, parentSuspense, isSVG) // 保留渲染生成的子树根DOM节点 initialVNode.el = subTree.el // 执行mounted钩子函数 if (m) { // 推入到postFlushCbs中 queuePostRenderEffect(m , parentSuspense) } instance.isMounted = true } else { // update component ellipsis... } }, prodEffectOptions) }
Update相关
Update
相关生命周期也是在setupRenderEffect
中进行挂载和执行, 主要有两个声明周期onBeforeUpdate
会在组件更新之前执行onUpdated
会在组件更新之后执行下面只贴刚刚上部分代码的else部分
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => { // ... if (xxx) { // ... } else { // 更新组件 // 获取组件实例上通过onBeforeUpdate勾子函数和onUpdate注册的钩子函数 let { next, vnode, bu, u } = instance; // next表示新的组件vnode if (next) { // 更新组件vnode节点信息 updateComponentPreRender(instance, next, optimized) } else { next = vnode } // 渲染新的子树vnode const nextTree = renderComponentRoot(instance) // 缓存旧的子树vnode const prevTree = instance.subTree // 更新子树vnode instance.subTree = nextTree // 执行beforeUpdate勾子函数 if (bu) { invokeArrayFns(bu) } // 组件更新的核心逻辑, 根据新旧子树vnode做patch(这个之后单独拿出来可以再详细讲一下) patch(prevTree, nextTree, hostParentNode(prevTree.el), getNextHostNode(prevTree, instance), parentSuspense, isSVG) // 缓存更新后的DOM节点 next.el = nextTree.el if (u) { // 推入到postFlushCbs中 queuePostRenderEffect(u, parentSuspense) } } // ... }
其实从上面代码可以看出, 因为
update
中与patch
的流程是完全相关的, 因此我们不能在updated勾子函数中更改数据, 因为这样会再次触发组件更新而引发不必要的性能问题, 这种情况下应当使用watch
相关API或computed
计算属性Unmount相关
Unmount
相关生命周期也是在unmountComponent
中进行挂载和执行, 主要有两个声明周期onBeforeUnmount
会在组件销毁之前执行onUnmounted
会在组件销毁之后执行const unmountComponent = (instance, parentSuspense, doRemove) => { const { bum, effects, update, subTree, um } = instance // 执行beforeMount勾子函数 if (bum) { invokeArrayFns(bum) } // 清理组件引用的effects副作用函数 if (effects) { for (let i = 0; i < effects.length, i++) { stop(effects[i]) } } // 如果一个异步组件在加载前就销毁了, 则不会注册副作用渲染函数 if (update) { stop(update) unmount(subTree, instance, parentSuspense, doRemove) } if (um) { // 推入到postFlushCbs中 queuePostRenderEffect(um, parentSuspense) } }
onErrorCaptured
如果组件上出现了错误, 则看是否有定义
onErrorCaptured
函数, 有的话直接调用. 如果返回true的话则不会向上传播, 没有则向上查找, 找到父组件的定义, 一直查找到根组件都没有的话, 则往控制台来输出未处理的错误function handleError(err, instance, type) { const contextVNode = instance ? instance.vnode : null if (instance) { let cur = instance.parent // 兼容vue2, 暴露组件实例给勾子函数 const exposedInstance = instance.proxy // 获取错误信息 const errorInfo = (process.env.NODE_ENV !== 'production') ? ErrorTypeStrings[type] : type // 尝试向上茶轴所有父组件, 执行errorCaptured勾子函数 while(cur) { const errorCapturedHooks = cur.ec if (errorCapturedHooks) { for (let i = 0; i < errorCapturedHooks.length; i++) { // 如果执行的errorCaptured钩子函数并返回true, 则停止向上查找 if (errorCapturedHooks[i](err, exposedInstance, errorInfo)) { return } } } cur = cur.parent } } // 一直没有捕获此错误, 向控制台输出 logError(err, type, contextVNode) }
onRenderTracked和onRenderTriggered
Vue3的新的API, 在渲染函数的
onTrack
和onTrigger
函数中执行instance.update = effect(function componentEffect() { // 创建或者更新组件 }, createDevEffectOptions(instance)) function createDevEffectOptions(instance) { return { scheduler: queueJob, onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc, e) : void 0, onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg.e) : void 0, } }
track
函数的实现function track(target, type, key) { // 执行一些依赖收集的操作 if (!dep.has(activeEffect)) { dep.add(activeEffect) activeEffect.deps.push(dep) // 在非生产环境下看当前的effect副作用函数中是否定义了onTrack函数 if ((process.env.NODE_ENV !== 'production') && activeEffect.options.onTrack) { // 执行onTrack函数 activeEffect.options.onTrack({ effect: activeEffect, target, type, key }) } } }
trigger
函数的实现function trigger(target, type, key, newValue) { // 添加要运行的effects集合 const run = (effect) => { if ((process.env.NODE_ENV !== 'production') && effect.options.onTrigger) { // 执行onTrigger effect.options.onTrigger({ effect, target, key, type, newValue, oldValue, oldTarget }) } if (effect.options.scheduler) { effect.options.scheduler(effect) } else { effect() } } // 遍历执行effects effect.forEach(run) }