原文地址:(A Complete Guide to Vue Lifecycle Hooks in Vue3 – LearnVue)

Vue3 与 Vue2 的生命周期钩子(hooks)非常相似——我们仍可以在相同的场景下使用相同(或类似)的钩子。

如果项目使用 Options API 方式构建,那就无须修改任何生命周期相关的代码。这是因为 Vue3 在设计时已对先前版本做了兼容处理。

然而,如果使用 Composition API 方式(构建大型 Vue 项目时非常有用)构建项目,生命周期钩子则会略有不同。

通过阅读本文,你将了解如何在 Options API 和 Composition API 中使用生命周期钩子,并且让你书写更优秀的代码。

冲!

什么是 Vue 的生命周期钩子

首先,让我们看一下 Options API 和 Composition API 的 Vue3 生命周期钩子示意图。在我们深入了解之前,能有一个整体上的概念。

基本上,每个主要的(main)Vue 生命周期事件会对应 2 个钩子函数,分别在事件开始前和事件结束后调用。在 Vue app 中你可以应用 4 个主要事件(8 个主要钩子(main hooks)函数)。

  • Creation ——组件创建时运行
  • Mounting ——挂载在 DOM 时运行
  • Updates ——响应数据修改时运行
  • Destruction ——元素销毁前运行

在 Options API 中使用生命周期钩子

使用 Options API 时,生命周期钩子暴露在 Vue 实例的选项中。不需要我们导入任何东西,只需调用对应的函数并书写相关生命周期的代码即可。

举个例子,假设我们想要访问 mounted()updated()生命周期钩子,代码可能如下所示:

<script>
   export default {
      mounted() {
         console.log('mounted!')
      },
      updated() {
         console.log('updated!')
      }
   }
</script>


是不是非常简单?

那么,接下来我们继续在 Composition API 中使用 Vue3 的生命周期钩子。

在 Composition API 中使用生命周期钩子

在 Composition API 中,我们需要先导入对应的生命周期钩子才能使用它。这么做,是为了让我们的项目尽可能的轻量化。

import { onMounted } from 'vue'


除了 beforeCreatecreated(被 setup方法本身代替),共有 9 个 Options API 生命周期相对应的方法可以在 setup 中使用。

  • onBeforeMount——挂载开始前调用
  • onMount——挂载后调用
  • onBeforeUpdate——当响应数据改变,且重新渲染前调用
  • onUpdated——重新渲染后调用
  • onBeforeUnmount——Vue 实例销毁前调用
  • onUnmounted——实例销毁后调用
  • onActivated——当 keep-alive 组件被激活时调用
  • onDeactivated——当 keep-alive 组件取消激活时调用
  • onErrorCaptured——从子组件中捕获错误时调用

当我们需要导入并访问这些钩子函数时,代码可能如下所示:

<script>
import { onMounted } from 'vue'

export default {
   setup () {
     onMounted(() => {
       console.log('mounted in the composition api!')
     })
   }
}
</script>


将 Vue2 代码更新为 Vue3 生命周期钩子

你可以方便的在 Vue3 Composition API docs 中查看 Vue2 到 Vue3 生命周期的映射。并且,我认为这也是明确如何改变和使用它们的最有效的方式之一。

  • beforeCreate setup()
  • created setup()
  • beforeMount onBeforeMount
  • mounted onMounted
  • beforeUpdate onBeforeUpdate
  • updated onUpdated
  • beforeDestroy onBeforeUnmount
  • destroyed onUnmounted
  • errorCaptured onErrorCaptured

深入了解每个生命周期钩子

现在我们已知两个重要的事情:

  1. 我们能够使用的各种生命周期
  2. 如何在 Options API 和 Composition API 中使用它们

接下来,让我们深入每个生命周期钩子中,了解如何使用它们,各个钩子中可以写什么样的代码,并且聊聊介于 Options API 和 Composition API 中的不同。

Creation 钩子——VueJS 生命周期的起始

Creation 钩子是你启动项目时触发的第一个事件。

beforeCreate() ——Options API

因为 created钩子是用来初始化响应数据和事件的,所以你不能在 beforeCreate钩子里访问任何组件的响应式数据和事件。

观察以下代码示例:

export default {
   data() {
     return {
       val: 'hello'
     }
   },
   beforeCreate() {
     console.log('Value of val is: ' + this.val)
   }
}


因为此时数据还未初始化,所以 val的值是 undefined,并且你也不能在此函数中调用组件中的其他方法。

如果你想要查看能使用的属性列表,我建议通过 console.log(this)查看哪些属性已经初始化。这对 Options API 的其他钩子函数也同样有效。

beforeCreate对那些不需要分配数据(data)的逻辑和 API 调用来说十分有用。如果我们此时对数据(data)赋值,那么这些值将在状态初始化后丢失。

created() – Options API

此时,我们已经可以访问组件的数据和事件。因此,将上例中的 beforeCreate替换为 created,我们能够看到输出有的改变。

export default {
   data() {
     return {
       val: 'hello'
     }
   },
   created() {
     console.log('Value of val is: ' + this.val)
   }
}


因为此时已经初始化,所以上例将输出 Value of val is: hello

当需要处理响应式数据读 / 写(reading/writing)时 created 方法非常有用。举个例子,如果你需要完成一个 API 调用并存储它的值,那么你应该将它写在这里。

这将比在 mounted 中处理来的更好,因为它在 Vue 的初始化进程中更早触发,并且你也能读写所有的数据。

那 Composition API 的 Creation 钩子呢?

在 Composition API 的生命周期钩子中,beforeCreatecreatedsetup()方法代替。这意味着你应该将对应的代码写在 setup 方法中。

将刚刚 created 生命周期钩子重写为如下所示

import { ref } from 'vue'

export default {
   setup() {
     const val = ref('hello')
     console.log('Value of val is: ' + val.value)
     return {
       val
     }
   }
}


Mounting 钩子——访问 DOM

mounting 钩子处理组件的挂载和渲染。这是我们在项目和应用程序中最常用的一组钩子。

beforeMount() and onBeforeMount()

在组件 DOM 实际渲染和挂载前触发。在此阶段,根元素(root element)还未存在。在 Options API 中,可以通过 this.$el访问。而在 Composition API 中,你必需通过 ref来指定根元素。

export default {
   beforeMount() {
     console.log(this.$el)
   }
 }


Composition 代码可能如下所示:

<template>
   <div ref='root'>
     Hello World
   </div>
</template>


通过 ref 访问的 script 代码如下:

import { ref, onBeforeMount } from 'vue'

export default {
   setup() {
      const root = ref(null)
      onBeforeMount(() => {
         console.log(root.value)
      })
      return {
         root
      }
    },
    beforeMount() {
      console.log(this.$el)
    }
 }


因为此时 app.$el还未创建,上述代码的输出将是 undefined。

于此同时,我们更推荐在 created()setup中执行你的 API 调用。两者具有相同的组件变量访问,但是 beforeMount 会在 created 之后调用。

mounted() and onMounted()

在组件第一次渲染时调用。此时,组件已经可以访问 DOM。

同样,在 Options API 中,我们使用 this.$el访问我们的 DOM,而在 Composition API 的生命周期钩子中,我们需要借助 refs 去访问相应 DOM。

import { ref, onMounted } from 'vue'


 export default {
   setup() {    /* Composition API */

     const root = ref(null)

     onMounted(() => {
       console.log(root.value)
     })


     return {
       root
     }
   },
   mounted() { /* Options API */
     console.log(this.$el)
   }
 }


Update 钩子——VueJS 生命周期中的响应

无论何时响应数据被修改,updated 生命周期事件都将触发,并且触发渲染更新。

beforeUpdate() and onBeforeUpdate()

在数据修改且组件重渲染之前执行。这是任何更改还未发生前,手动修改 DOM 的好地方。

beforeUpdate对追踪组件的编辑次数时非常有用,甚至可以通过追踪对应的操作来创建一个 “撤销” 功能。

updated() and onUpdated()

updated 方法在 DOM 更新后调用一次。这是一段 beforeUpdate 和 updated 的基本代码

 <template>
    <div>
      <p>{{val}} | edited {{ count }} times</p>
      <button @click='val = Math.random(0, 100)'>Click to Change</button>
    </div>
 </template>


相对应的 script 代码

 export default {
   data() {
      return {
        val: 0
      }
   },
   beforeUpdate() {
      console.log("beforeUpdate() val: " + this.val)
   },
   updated() {
      console.log("updated() val: " + this.val
   }
 }


import { ref, onBeforeUpdate, onUpdated } from 'vue'

 export default {
   setup () {
     const count = ref(0)
     const val = ref(0)

     onBeforeUpdate(() => {
       count.value++;
       console.log("beforeUpdate");
     })

     onUpdated(() => {
       console.log("updated() val: " + val.value)
     })

     return {
       count, val
     }
   }
 }


这些方法很有用,但多数情况我们可能会通过监听器(watchers)去检测对应数据的改变。因为监听器可以很好的提供数据更改时的旧值和新值。

另一种方式是通过计算属性来改变元素的状态。

Destruction 钩子——清理

destruction 钩子在组件被移除并需要清理一些待释放的功能时使用。这是删除事件监听并且防止内存溢出的好地方。

beforeUnmount() and onBeforeUnmounted()

触发在组件开始销毁之前,在此会进行绝大多数的清理工作。在此阶段,你的组件仍然拥有所有的功能,任何东西都还未被销毁。

举一个删除事件监听的例子,Options API 方式如下

export default {
   mounted() {
     console.log('mount')
     window.addEventListener('resize', this.someMethod);
   },
   beforeUnmount() {
     console.log('unmount')
     window.removeEventListener('resize', this.someMethod);
   },
   methods: {
      someMethod() {
         // do smth
      }
   }
}


Composition API 方式

 import { onMounted, onBeforeUnmount } from 'vue'

 export default {
   setup () {

     const someMethod = () => {
       // do smth
     }

     onMounted(() => {
       console.log('mount')
       window.addEventListener('resize', someMethod);
     })

     onBeforeUnmount(() => {
       console.log('unmount')
       window.removeEventListener('resize', someMethod);
     })

   }
 }


可以在 Vite,vue-cli,或任何支持热加载的环境中查看这些行为的执行。当你更新代码时,你的组件会销毁,并重新挂载。

unmounted() and onUnmounted()

此时,大多数组件和它的属性已经销毁,所以你能做的不多。同样,我打印一段数据去确切观察发生了什么。

import { onUnmounted } from 'vue'

export default {
  setup () { /* Composition API */

    onUnmounted(() => {
      console.log('unmounted')
    })

  },
  unmounted() { /* Options API */
    console.log('unmounted')
  }
}


Activation 钩子——管理 Keep-Alive 组件

keep-alive 标签是对动态组件的包装元素。它保存一段组件实例的缓存引用,如此 Vue 就不需要在每次动态组件(dynamic component)改变时创建一个新的实例。

对于这种特殊的使用情况,Vue 提供了两个生命周期钩子。

activated() and onActivated()

无论何时动态组件被 “重新激活”(意味着此时是动态组件激活视图)时该钩子方法都将被调用。

举个例子,如果我们使用 keep-alive 管理不同的 tab 视图,每次我们切换 tab 时,当前的 tab 将会触发 activated 钩子。

假设我们有以下用 keep-alive 包装的 动态组件 setup

<template>
   <div>
     <span @click='tabName = "Tab1"'>Tab 1 </span>
     <span @click='tabName = "Tab2"'>Tab 2</span>
     <keep-alive>
       <component :is='tabName' class='tab-area'/>
     </keep-alive>
   </div>
</template>

<script>
import Tab1 from './Tab1.vue'
import Tab2 from './Tab2.vue'

import { ref } from 'vue'

export default {
  components: {
    Tab1,
    Tab2
  },
  setup () { /* Composition API */
    const tabName = ref('Tab1')

    return {
      tabName
    }
  }
}
</script>


在我们的 Tab1.vue组件中,我们能够如下访问 activation 钩子。

<template>
 <div>
 <h2>Tab 1</h2>
 <input type='text' placeholder='this content will persist!'/>
 </div>
</template>

<script>
import { onActivated } from 'vue'

export default {
 setup() {
    onActivated(() => {
       console.log('Tab 1 Activated')
    })
 }
}
</script>


deactivated() and onDeactivated()

和你想象的一样,视图不在动态组件中继续保持激活时触发此钩子。

该钩子在某些情况下非常有用,例如特定视图失去焦点时,保存用户数据和触发动画。

我们能够如下所示,捕获该生命周期钩子

import { onActivated, onDeactivated } from 'vue'

export default {
  setup() {
    onActivated(() => {
       console.log('Tab 1 Activated')
    })

    onDeactivated(() => {
       console.log('Tab 1 Deactivated')
    })
  }
}


现在,当我们切换 tab 时,每个动态组件的状态都将被保存。

好耶!

Vue3 Debug Hooks

Vue3 为我们提供了两个调试时的钩子:

  1. onRenderTracked
  2. onRenderTriggered

这两个事件都携带一个 DebuggerEvent 参数,以允许我们获悉是什么触发了 Vue 实例的重新渲染。

export default {
    onRenderTriggered(e) {
       debugger
       // 检测什么依赖造成了组件的重新渲染
    }
}


总结

无论是使用 Options API 还是 Composition API,不能仅知道你能用什么生命周期,还要知道为什么使用它们。

许多问题都能在多个生命周期钩子中解决。但是,最好知道哪个生命周期是最适合你当下的情况。不管怎样,你都需要深思熟虑,并对使用特定的生命周期有充足的理由。

我希望借此文章对你理解生命周期函数,并且在你项目中应用它们时有所帮助。

Happy coding!