Vue中组件的生命周期实现机制
2021-10-20
| 2023-2-19
0  |  0 分钟
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函数替代了beforeCreatecreated函数, 这也使得我们可以在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, 在渲染函数的onTrackonTrigger函数中执行
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) }

声明周期总览

notion image
源码
  • Vue源码
  • Vue
  • Vue中的依赖注入实现机制Vue中的AST
    目录