一、Vue3 生命周期全览
组件的生命周期是由一系列的钩子函数组成的,这些钩子在组件的不同创建和销毁阶段被调用。这些生命周期钩子提供了在特定时刻添加自定义行为的机会,例如在组件挂载到DOM后或数据更新前后执行代码。
Options API 与 Composition API 对照表:
Options API | Composition API | 触发时机 |
---|---|---|
beforeCreate | 无需映射 | 在 setup() 之前执行 |
created | 无需映射 | 在 setup() 之前执行 |
beforeMount | onBeforeMount | 挂载开始之前 |
mounted | onMounted | 挂载完成后 |
beforeUpdate | onBeforeUpdate | 响应式数据变化导致重新渲染前 |
updated | onUpdated | 重新渲染完成后 |
beforeUnmount | onBeforeUnmount | 组件卸载前 |
unmounted | onUnmounted | 组件卸载后 |
errorCaptured | onErrorCaptured | 捕获子孙组件错误时 |
二、Composition API 生命周期使用
基础示例:
<script setup>
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
// 组件挂载阶段
onBeforeMount(() => {
console.log('挂载前 - DOM 尚未创建')
})
onMounted(() => {
console.log('挂载完成 - 可访问 DOM')
window.addEventListener('resize', handleResize)
})
// 组件更新阶段
onBeforeUpdate(() => {
console.log('数据变化,即将重新渲染')
})
onUpdated(() => {
console.log('重新渲染完成')
})
// 组件卸载阶段
onBeforeUnmount(() => {
console.log('组件即将卸载')
})
onUnmounted(() => {
console.log('组件已卸载')
window.removeEventListener('resize', handleResize)
})
function handleResize() {
console.log('窗口大小变化')
}
</script>
三、关于 Hooks
在Vue3中,Hooks是基于Composition API实现的一组可复用的函数。这些函数可以“钩入”Vue组件的生命周期,让我们能够在组件的不同生命周期阶段执行特定的逻辑。
1. Hooks的实现原理
在Vue3中,Hooks通过setup函数来使用。setup函数是Vue3组件中的一个新的生命周期函数,它在组件实例被创建之前调用,并且接收两个参数:props和context。在setup函数中,我们可以定义和返回组件中需要使用的响应式数据、方法、计算属性等,而这些都可以通过Hooks来实现。
2 .Hooks的使用场景
-
逻辑复用:当多个组件需要共享相同的逻辑时,我们可以将这些逻辑封装成一个Hook,然后在需要的组件中导入并使用它。这样可以避免代码重复,提高代码的复用性。
-
逻辑拆分:对于复杂的组件,我们可以使用Hooks将组件的逻辑拆分成多个独立的函数,每个函数负责处理一部分逻辑。这样可以使组件的代码更加清晰、易于维护。
-
副作用管理:Hooks中的函数可以访问组件的响应式数据,并且可以在组件的生命周期中执行副作用操作(如定时器、事件监听等)。通过使用Hooks,我们可以更好地管理这些副作用操作,确保它们在组件卸载时得到正确的清理。
四、自定义 Hook 开发指南
1. 鼠标位置跟踪 Hook:
// hooks/useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMousePosition() {
const x = ref(0)
const y = ref(0)
const updatePosition = (e) => {
x.value = e.clientX
y.value = e.clientY
}
onMounted(() => {
window.addEventListener('mousemove', updatePosition)
})
onUnmounted(() => {
window.removeEventListener('mousemove', updatePosition)
})
return { x, y }
}
// 组件中使用
<script setup>
import { useMousePosition } from './hooks/useMousePosition'
const { x, y } = useMousePosition()
</script>
<template>
鼠标位置:{{ x }}, {{ y }}
</template>
2. 异步数据请求 Hook:
// hooks/useFetch.js
import { ref, onBeforeUnmount } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
let abortController = new AbortController()
const fetchData = async () => {
try {
loading.value = true
const response = await fetch(url, {
signal: abortController.signal
})
data.value = await response.json()
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
onBeforeUnmount(() => {
abortController.abort()
})
return {
data,
error,
loading,
fetchData
}
}
// 组件中使用
<script setup>
import { useFetch } from './hooks/useFetch'
const { data, loading, error, fetchData } = useFetch('/api/data')
onMounted(() => {
fetchData()
})
</script>
五、最佳实践与常见问题
1. 生命周期使用原则:
-
在
onMounted
中访问 DOM/初始化第三方库 -
在
onUnmounted
中清理副作用(定时器、事件监听) -
避免在
onUpdated
中修改状态(可能导致无限循环)
2. 自定义 Hook 规范:
-
命名以
use
开头(如useDarkMode
) -
单一职责原则(一个 Hook 只解决一个问题)
-
返回响应式数据时使用
toRefs
:return toRefs(reactive({ x, y }))
3. 常见问题解决:
Q:如何在 setup 中访问 this?
-
不需要也不应该访问 this,所有上下文通过 Composition API 获取
Q:多个 Hook 之间如何共享状态?
// 共享状态 Hook
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
return {
count,
increment
}
}
// 组件 A
const { count: countA } = useCounter()
// 组件 B
const { count: countB } = useCounter(10)
Q:如何处理异步操作的竞态条件?
// 在 Hook 中添加请求标识
let requestId = 0
const fetchData = async () => {
const currentId = ++requestId
const res = await fetch(url)
if (currentId === requestId) {
// 处理有效响应
}
}
六、高级应用场景
1. 组合多个 Hook:
// 组件逻辑
const { user } = useUser()
const { posts, loading } = usePosts(user.value.id)
const { width } = useWindowSize()
2. 带参数的动态 Hook:
// 根据路由参数变化的 Hook
export function useRouteData() {
const route = useRoute()
const id = computed(() => route.params.id)
const { data, fetch } = useFetch(`/api/${id.value}`)
watch(id, fetch, { immediate: true })
return { data }
}
3. 生命周期可视化调试:
// 开发环境专用 Hook
export function useLifecycleLogger(name) {
if (process.env.NODE_ENV === 'development') {
onBeforeMount(() => console.log(`${name} - beforeMount`))
onMounted(() => console.log(`${name} - mounted`))
// 其他生命周期同理...
}
}
七、TypeScript 增强示例
1. 类型化生命周期:
interface Position {
x: number
y: number
}
export function useMousePosition(): {
x: Ref<number>
y: Ref<number>
} {
// 实现同上
}
2. 泛型请求 Hook:
export function useFetch<T>(url: string) {
const data = ref<T | null>(null)
// 其他逻辑...
return { data }
}
// 使用示例
interface User {
id: number
name: string
}
const { data } = useFetch<User[]>('/api/users')