Vue3 生命周期与Hooks
一、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.clientXy.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 = trueconst 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 = 0const fetchData = async () => {const currentId = ++requestIdconst 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: numbery: 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: numbername: string
}const { data } = useFetch<User[]>('/api/users')