深入浅析Vue3中的生命周期钩子函数

前言

在前端开发中,Vue 凭借其简洁的语法和高效的响应式系统,成为开发者构建用户界面的首选框架。随着 Vue3 的正式发布,其引入的组合式 API 彻底改变了开发者的编码逻辑与组件设计模式。其中,在 Vue3 的世界里,Hooks 函数作为组合式 API 的核心组件犹如幕后的指挥家,通过函数化的编程范式,默默掌控着组件从诞生到销毁的每一个关键阶段。它们不仅是理解组件运行机制的关键,更是开发者实现业务逻辑、优化代码结构的得力工具。本文将深入探讨 Vue3 生命周期钩子函数,从基础概念到高级应用,结合生动比喻与代码示例,全面了解其用法、作用以及在实际开发中的应用场景。

一、基础概念与核心特性

1.1 什么是 Vue3 Hooks

Vue3 Hooks 是组合式 API 提供的函数式接口,用于在组件中声明和管理响应式状态、在特定生命周期阶段执行异步操作、监听数据变化并触发回调、计算属性以及复用逻辑代码。与选项式 API 的 data、methods、computed 等选项不同,Hooks 通过函数调用的方式,将功能模块化为独立的代码块。可以将 Hooks 想象为"乐高积木"------每个 Hook 函数代表一块积木,开发者可以根据需求自由组合这些积木,构建出复杂的功能结构。例如:

函数示例 主要用途
ref() 基础积木,用于创建响应式数据。
watch() 观察积木,用于监听数据变化。
onMounted() 生命周期积木,用于在组件挂载时执行逻辑。

1.2 Vue3 生命周期钩子函数

Vue3 组件的生命周期,指的是从组件被创建、挂载到 DOM,再到数据更新、最终被销毁的整个过程,如下图所示。而在这个过程中的各个阶段,Vue 都会调用相应的函数,这些函数被称为生命周期钩子函数或简称为钩子函数。它们的作用是在特定的事件触发时,由系统捕获并执行相应的操作,为用户提供在各个阶段添加自定义代码的机会。

Vue3 提供了一系列的生命周期钩子函数,如下表所示,让开发者能够在特定阶段执行自定义代码。这些钩子函数就像是在组件生命周期旅程中的一个个站点,开发者可以在这些站点上添加自己的 "任务",它允许我们更灵活地组织和重用代码,如下图所示。这些钩子函数使得组件的逻辑可以更清晰地分离和复用,特别是在处理生命周期和副作用时非常有用。

序号 函数 简要说明
1 onBeforeMount 组件挂载之前调用。此时 DOM 元素还未创建,可用于在挂载前做一些准备工作
2 onMounted 组件挂载后调用。此时 DOM 元素已经创建并可以被访问,可用于操作 DOM 或进行一些异步请求。
3 onBeforeUpdate 组件更新之前调用。可用于在更新前进行一些操作,如记录旧数据状态。
4 onUpdated 组件更新后调用。可用于在更新后执行一些操作,如依赖于新数据的逻辑。
5 onBeforeUnmount 组件卸载之前调用。此时组件仍然可以访问,可用于在组件销毁前进行一些清理工作,如移除事件监听器。
6 onUnmounted 组件卸载后调用。此时组件已经被销毁,可用于在组件销毁后进行一些清理工作,如清理定时器。

组件实例内部储存的属性,一般是响应式数据,响应式数据一更新,这个组件实例也就更新了,如下图所示。

html 复制代码
<script setup>
//生命周期
import {ref, onMounted, onUpdated} from 'vue';

let msg = ref("hello");

onMounted(function () {
  console.log("----------组件挂载完毕-------------")
  console.log(msg.value)
});

onUpdated(function () {
  console.log("----------组件更新后-------------")
  console.log(msg.value)
});
</script>

<template>
  <input v-model="msg" type="text"><br>
  {{ msg }}
</template>

二、创建阶段:组件的初始化

组件的生命周期从初始化开始。在这个阶段,Vue 会创建组件实例,设置响应式系统,并执行初始化相关的钩子函数,让我们详细看看这个阶段的实现。

2.1 setup

setup() 这不是一个传统的钩子函数,是一个全新的 Vue 组件选项,它是 Composition API 的核心,也是整个组件生命周期中最早执行的,用于初始化组件实例。它是在组件创建之前执行的,通常用于初始化组件的状态和定义组合逻辑。setup 函数可以接收两个参数:props 和 context,其中 props 是父组件传递给当前组件实例的属性,而 context 则包含了一些 helper 方法和组件选项(如 attrs、slots 和 emit 等)。

javascript 复制代码
export default {
    setup(props, context) {
    // 透传 Attributes(非响应式的对象,等价于 $attrs)
        console.log(context.attrs)
        // 插槽(非响应式的对象,等价于 $slots)
        console.log(context.slots)
        // 触发事件(函数,等价于 $emit)
        console.log(context.emit)
        // 暴露公共属性(函数)
        console.log(context.expose
    }
}

在 setup 中,我们可以使用 Vue 3 提供的多个工具函数来定义响应式数据、监听生命周期钩子、处理计算属性、声明事件处理函数等。使用 setup 函数的优点是可以将相似的逻辑组织在一起,便于代码的维护和重用。此外,在使用 script setup 语法糖时,不再需要返回任何东西,因为脚本中的顶层绑定现在是模板上下文的一部分,代码变得更简洁。

html 复制代码
<script setup>
import { ref } from 'vue';

// 创建一个初始值为 0 的响应式引用
const count = ref(0);
const increment = () => {
  count.value++;
};
</script>

<template>
  <div>{{ count }}</div>
  <button @click="increment">Increment</button>
</template>

在上面的例子中,使用了 ref() 创建一个响应式的值引用,它接受一个值作为参数,并返回一个包含该值的响应式引用对象。这个引用对象有一个 .value 属性,用于获取或设置其内部值。初次之外,还可以使用 reactive() 创建一个响应式的对象,它接受一个普通 JavaScript 对象作为参数,并返回一个经过代理的响应式版本的该对象。与 ref() 不同,不需要使用 .value 来访问或修改对象的属性。

html 复制代码
<script setup>
import { reactive } from 'vue';

// 创建一个响应式对象
const state = reactive({
  message: 'Hello Vue!',
  count: 0
});

const updateMessage = () => {
  state.message = 'Updated message!';
};
</script>

<template>
  <p>{{ state.message }}</p>
  <button @click="updateMessage">Update Message</button>
</template>

三、挂载阶段:组件的渲染与插入

在挂载阶段,Vue 实例被添加到 DOM 中,并渲染成实际的页面元素,让我们详细看看这个阶段的实现。

3.1 onBeforeMount

在组件挂载到 DOM 之前,onBeforeMount 函数被调用。此时,模板已经编译完成但尚未挂载到真实的 DOM 中,即将首次执行 DOM 渲染过程,我们可以在这个阶段可以用来在组件挂载前执行一些初始化操作。不过由于 DOM 尚未挂载,直接操作 DOM 的意义不大。下面是一个使用 onBeforeMount 的例子:

html 复制代码
<script setup>
import { onBeforeMount, ref } from 'vue';

onBeforeMount(() => {
  // 在组件挂载前加载数据
  console.log('组件即将挂载');
});
</script>

上面的例子中,onBeforeMount() 函数会在组件模板挂载到 DOM 之前执行,在控制台输出"组件即将挂载"。

3.2 onMounted

在 Vue 3 中,onMounted 是一个生命周期钩子函数,用于在组件实例被挂载到 DOM 上后执行特定操作。onMounted 只会在组件第一次挂载时调用,如果组件的状态发生变化而未被卸载,它不会再次调用,这是执行异步操作或初始化事件监听器的好地方,通常用于获取数据和初始化页面状态等操作。下面是一个简单的示例,展示如何使用 onMounted:

html 复制代码
<script setup>
import { ref, onMounted } from 'vue';

const el = ref()

onMounted(() => {
    console.log('组件已挂载到 DOM');
    // 可以在这里进行数据获取或其他操作
    // el.value
});
</script>

<template>
  <div ref="el"></div>
</template>

【注意】onMounted() 在服务器端渲染期间不会被调用,因为它依赖于浏览器环境,不会在没有 DOM 的环境中执行。

在这个阶段,可以确保组件的 DOM 已经准备好,我们可以访问真实的 DOM 元素,进行一些依赖于 DOM 的操作,如操作 DOM 元素的样式、绑定事件监听器等。

html 复制代码
<script setup>
import { ref, onMounted,onUnmounted } from 'vue'

const handleResize = () => {
  console.log('窗口尺寸变化:', window.innerWidth);
}

onMounted(() => {
  window.addEventListener('resize', handleResize);
});

// 清理事件监听器(防止内存泄漏)
onUnmounted(() => {
  window.removeEventListener('resize', handleResize);
});
</script>

虽然 onMounted 是进行 DOM 操作的理想位置,但应避免在其中执行过多复杂的逻辑,以免延长挂载时间,影响用户体验。onMounted 不适合用于初始化组件的基本状态(如 props),它更适合执行需要依赖于 DOM 或外部数据的操作。如果在 onMounted 中修改响应式数据,确保使用 Vue 提供的响应式 API(如 ref 和 reactive),以保持数据的响应性。

四、更新阶段:组件数据变化

当组件的响应式数据发生变化时,Vue 会重新渲染组件,让我们详细看看这个阶段的实现。

4.1 onBeforeUpdate

当组件的数据发生变化,重新渲染之前,onBeforeUpdate 函数被调用。在这个阶段,组件的 data 已经更新,但 DOM 尚未更新,用于在数据更新前进行一些准备工作,例如保存当前 DOM 的状态,以便在更新后进行对比或恢复。下面是一个使用 onBeforeUpdate() 的例子:

html 复制代码
<script setup>
import {onBeforeUpdate, ref} from 'vue'

const count = ref(1)

onBeforeUpdate(() => {
  console.log('Before update', count.value)
})

const handleClick = () => {
  count.value++
}
</script>

<template>
  <div>
    <p>{{ count }}</p>
    <button @click="handleClick">增加</button>
  </div>
</template>

上面的例子中,我们使用 ref 函数创建了一个 count 变量,并在模板中使用{{ count }} 渲染出文本,然后使用 onBeforeUpdate() 函数,在组件更新之前输出当前消息的值。注意,onBeforeUpdate() 钩子并不阻止更新的发生,它只是允许我们在更新发生前执行一些代码。

4.2 onUpdated

onUpdated 表示组件在更新之后的事件,它会在组件的状态发生改变之后、视图重新渲染之后执行。此时适合进行一些依赖于更新后 DOM 状态的操作,例如重新计算一些基于新 DOM 结构的布局信息,或者对更新后的 DOM 进行进一步的样式调整等等。下面是一个使用 onUpdated() 的例子:

html 复制代码
<script setup>
import {ref, onBeforeUpdate, onUpdated} from 'vue';

const message = ref('初始消息');

const updateData = () => {
  message.value = '更新后的消息';
};

onBeforeUpdate(() => {
  console.log('数据即将更新,当前消息:', message.value);
});

onUpdated(() => {
  console.log('数据已更新,DOM 已重新渲染');
});
</script>

<template>
  <div @click="updateData">
    <p>{{ message }}</p>
  </div>
</template>

【注意】onUpdated() 在服务端渲染期间不会被调用,因为它依赖于 DOM 更新,而在客户端首次加载时才生成完整的 HTML。因此,确保 onUpdated 钩子中的代码不会在没有 DOM 的环境中引发错误。 但需要注意的是,在 onUpdated 函数中避免不必要的状态更新,以免陷入无限循环。

五、销毁阶段:组件的卸载与清理

在销毁阶段,Vue实例被销毁。当组件被卸载时(比如通过 v-if 移除组件或手动调用 unmount),Vue 会执行一系列清理工作,让我们看看卸载阶段的生命周期钩子实现。

5.1 onBeforeUnmount

在组件即将被卸载(从父组件中移除或组件所在路由切换等情况)之前,onBeforeUnmount 数被调用。在这个阶段,组件仍然存在,可以访问组件的属性和方法,可用于清理组件在运行过程中创建的一些副作用,例如清除定时器、解绑事件监听器等,以避免内存泄漏。

html 复制代码
<script setup>
import { onBeforeUnmount, ref } from 'vue';

onBeforeUnmount(() => {
  // 在组件卸载前停止计数
    console.log('组件即将卸载,清除定时器');
})
</script>

注意】确保在组件生命周期的适当阶段进行清理是非常重要的,特别是在处理定时器、WebSocket 连接或外部资源时,onBeforeUnmount 提供了一个执行这些清理任务的理想时机,可以确保重新挂载时环境干净。

5.2 onUnmounted

onUnmounted() 函数在组件被成功卸载后调用。此时,组件已经从 DOM 中移除,相关的事件监听器等也已被解绑。在这个阶段,组件的生命周期基本结束,理论上在此处已无需进行复杂操作,不过可以用于记录组件卸载相关的日志信息。。

html 复制代码
<script setup>
import { ref, onBeforeUnmount, onUnmounted } from 'vue';

const message = ref('组件内容');
let timer;

const startTimer = () => {
  timer = setInterval(() => {
    console.log('定时器运行中');
  }, 1000);
};

startTimer();

onBeforeUnmount(() => {
  clearInterval(timer);
  console.log('组件即将卸载,清除定时器');
});

onUnmounted(() => {
  console.log('组件已卸载');
});
</script>

<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

【注意】onUnmounted() 在服务端渲染期间不会被调用在实际应用中,可能会有更复杂的场景,例如订阅 WebSocket 连接、注册全局事件监听器等,在这些情况下,onUnmounted() 钩子同样可以用来确保在组件不再使用时进行适当的清理工作。

六、错误捕获阶段:组件错误处理

6.1 errorCaptured

onErrorCaptured 在子孙组件出现错误时被调用,提供了捕获和处理错误的机会。在捕获到错误时,提示用户友好的错误信息。除此之外,还可以将错误信息记录到日志系统,便于后续分析。

html 复制代码
<script setup lang="ts">
import {onErrorCaptured} from "vue";

onErrorCaptured((err, vm, info) => {
  console.error('捕获到错误:', err);
  console.error('错误信息:', info);
  // 返回false可以阻止错误继续向上传播
  return false;
})
</script>

七、附录

7.1 选项式 API 与组合式 API 对比

为了更好地理解和使用Vue 3的生命周期,下面是一个完整的生命周期钩子函数列表:

阶段 执行时机 选项式API钩子 组合式API钩子
创建阶段 组件实例初始化前 beforeCreate
组件实例创建后 created setup
挂载阶段 在组件挂载到 DOM 之前调用。 beforeMount onBeforeMount
在组件挂载完成后调用。 mounted onMounted
更新阶段 在组件即将更新之前调用。 beforeUpdate onBeforeUpdate
在组件更新后调用。 updated onUpdated
卸载阶段 在组件即将卸载之前调用。 beforeUnmount onBeforeUnmount
在组件卸载后调用。 unmounted onUnmounted
错误捕获阶段 当捕获一个来自子孙组件的错误时被调用。 errorCaptured onErrorCaptured

通过对比可以看出,组合式 API 中的生命周期钩子与选项式 API 中的钩子功能一致,只是命名和使用方式不同。在实际开发中,可根据项目风格和个人习惯选择合适的 API 风格。

八、小结

Vue3 的生命周期钩子函数为开发者提供了在组件不同生命周期阶段执行自定义逻辑的能力,通过 setup 函数和一系列的钩子函数,我们可以轻松地管理组件的生命周期,并将逻辑分离到可重用的函数中。通过合理运用这些钩子函数,我们能够更好地控制组件的行为,实现复杂的业务逻辑,优化组件性能。在实际开发中,深入理解每个钩子函数的执行时机和作用,根据具体需求选择合适的钩子函数进行代码编写,是构建高质量 Vue3 应用的关键。

相关推荐
Warren9815 分钟前
Vue2博客项目笔记(第一天)
java·开发语言·javascript·vue.js·windows·笔记·ecmascript
王者鳜錸1 小时前
VUE+SPRINGBOOT从0-1打造前后端-前后台系统-邮箱重置密码
前端·vue.js·spring boot
小白白一枚1113 小时前
vue和react的框架原理
前端·vue.js·react.js
字节逆旅3 小时前
从一次爬坑看前端的出路
前端·后端·程序员
若梦plus4 小时前
微前端之样式隔离、JS隔离、公共依赖、路由状态更新、通信方式对比
前端
若梦plus4 小时前
Babel中微内核&插件化思想的应用
前端·babel
若梦plus4 小时前
微前端中微内核&插件化思想的应用
前端
若梦plus4 小时前
服务化架构中微内核&插件化思想的应用
前端
若梦plus4 小时前
Electron中微内核&插件化思想的应用
前端·electron