六、vue3 生命周期与自定义 Hooks
·
文章目录
一、vue3生命周期
如果把组件看作一个活生生的“生命体”,生命周期钩子就是这个生命体在出生、长大、生病、死亡各个阶段自动触发的“警报器”。理解它们,是控制组件行为、性能优化的核心。
1. Vue2 和 Vue3 生命周期对比
Vue3 对生命周期做了一次优雅的翻新。最大的变化是:去掉了 beforeCreate 和 created,直接用 setup 代替;同时,所有的销毁钩子更名为“卸载”(Unmount),语义更加准确。
核心钩子对应表
| 阶段 | Vue2 选项式 API | Vue3 组合式 API | 触发时机与核心用途 |
|---|---|---|---|
| 创建 | beforeCreate created |
直接写在 setup 顶层 |
组件实例刚创建。通常在这里发送初始的 Ajax 请求、初始化变量 |
| 挂载 | beforeMountmounted |
onBeforeMountonMounted |
虚拟 DOM 挂载到真实页面上。onMounted 阶段才可以安全地操作 DOM |
| 更新 | beforeUpdateupdated |
onBeforeUpdateonUpdated |
页面依赖的响应式数据发生改变,引发界面重新渲染 |
| 销毁 | beforeDestroydestroyed |
onBeforeUnmountonUnmounted |
组件被切换或隐藏(如 v-if="false")。必须在这里清除定时器、解绑全局事件,否则会导致内存泄漏 |
2. Vue3 生命周期的标准写法
在 Vue3 中,生命周期钩子变成了按需引入的函数。你可以在同一个组件里多次调用同一个钩子,它们会按照编写顺序依次执行。
完整代码示范 (LifecycleDemo.vue)
<template>
<div>
<h3>当前计数:{{ count }}</h3>
<button @click="count++">点击更新界面</button>
</div>
</template>
<script setup>
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
// 1. 替代了 created:直接在 setup 顶层写代码
console.log('1. 组件创建了!(相当于 created)')
const count = ref(0)
// 模拟异步获取数据
const loadData = () => {
console.log('Ajax 请求发送中...')
}
loadData()
// 2. 挂载完成
onMounted(() => {
console.log('2. 页面渲染完毕了!(mounted),现在可以获取 DOM 元素')
// 比如在这里开启一个全局监听
window.addEventListener('resize', onResize)
})
// 3. 数据更新
onUpdated(() => {
console.log('3. 界面因为 count 改变而重新渲染了!(updated)')
})
// 4. 卸载/销毁
onUnmounted(() => {
console.log('4. 组件死掉了!(unmounted)')
// 极其重要:必须在这里清理垃圾,防止内存泄漏
window.removeEventListener('resize', onResize)
})
const onResize = () => {
console.log('窗口大小变了')
}
</script>
二、自定义 Hooks(组合式 API 的灵魂)
1. 什么是 Hooks?为什么要用它?
在 Vue2 中,如果我们想在多个组件之间复用一段公共逻辑(比如:监听鼠标坐标、倒计时功能),我们只能用 Mixin(混入)。但 Mixin 存在严重的致命缺点:数据来源不明确、容易发生命名冲突,项目放大后极其难维护。
Vue3 的自定义 Hooks 彻底解决了这个问题。
Hooks 的本质:利用 Vue3 的响应式 API(
ref,reactive)和生命周期钩子,把一段完整的业务逻辑封装成一个独立的.js或.ts函数。
2. 封装一个“实时获取鼠标坐标”的 Hook
第一步:创建 Hook 文件 (src/hooks/useMousePosition.js)
约定俗成,Hooks 文件和函数通常以
use开头。
import { ref, onMounted, onUnmounted } from 'vue'
// 暴露一个公共函数
export function useMousePosition() {
// 1. 在 Hook 内部定义响应式数据
const x = ref(0)
const y = ref(0)
// 2. 复杂的业务逻辑
const updatePosition = (e) => {
x.value = e.pageX
y.value = e.pageY
}
// 3. 配合生命周期:挂载时监听,销毁时移除
onMounted(() => {
window.addEventListener('mousemove', updatePosition)
})
onUnmounted(() => {
window.removeEventListener('mousemove', updatePosition)
})
// 4. 核心:把数据和方法返回出去供组件使用
return { x, y }
}
第二步:在任意组件中轻松引入和使用 (MyComponent.vue)
<template>
<div class="box">
<h4>当前鼠标坐标:X: {{ x }}, Y: {{ y }}</h4>
</div>
</template>
<script setup>
// 引入我们的自定义 Hook
import { useMousePosition } from '../hooks/useMousePosition'
// 像解构普通对象一样拿出来用,变量来源一目了然,绝不冲突!
const { x, y } = useMousePosition()
</script>
3. 致命大坑:Hooks 里的响应式丢失问题
这是初学者写 Hooks 最容易踩的逻辑地雷:不要在 Hook 内部用 reactive 声明对象并直接解构返回!
错误示范:
// 在 useMousePosition.js 内部
import { reactive } from 'vue'
export function useMousePosition() {
const pos = reactive({ x: 0, y: 0 })
// ...监听逻辑
// 错误返回:直接返回 pos,或者 return { ...pos }
return pos
}
- 为什么错误?
如果在组件里写const { x, y } = useMousePosition(),在解构的那一瞬间,x和y就会退化成普通的数字 0,彻底失去响应式!鼠标再怎么动,界面也不会变。
两种标准正确写法:
- 解法一(最推荐):像我们上面的正确示范一样,内部全部用独立的
ref定义,最后包裹在一个普通对象里返回。 - 解法二:内部用
reactive,但返回时使用toRefs函数包装一层:
import { reactive, toRefs } from 'vue'
// ...内部监听逻辑不变
return toRefs(pos)
// toRefs 会把 reactive 内部的属性全部转为 ref,这样在组件中解构也不会丢失响应式!
更多推荐

所有评论(0)