【译】Vue3生命周期钩子(hooks)完整指南

16,252 阅读9分钟

原文地址:(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生命周期钩子示意图。在我们深入了解之前,能有一个整体上的概念。

Source: LearnVue

基本上,每个主要的(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!