生命周期
【官方解释】每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。
下表是对Vue 3组合式API的生命周期钩子的理解和用法的简要说明:
钩子函数 | 说明 | 用法示例 |
---|---|---|
onBeforeMount | 在组件挂载之前被调用,此时模板编译已完成,但尚未将组件插入到DOM中 | onBeforeMount(() => { // 在组件挂载之前的逻辑 }) |
onMounted | 在组件挂载到DOM后被调用 | onMounted(() => { // 在组件挂载到DOM后的逻辑 }) |
onBeforeUnmount | 在组件卸载之前被调用,此时组件仍然在DOM中 | onBeforeUnmount(() => { // 在组件卸载之前的逻辑 }) |
onUnmounted | 在组件卸载之后被调用,此时组件已从DOM中移除 | onUnmounted(() => { // 在组件卸载后的逻辑 }) |
onBeforeUpdate | 在组件更新之前被调用,可用于在更新之前访问旧的DOM状态或数据 | onBeforeUpdate(() => { // 在组件更新之前的逻辑 }) |
onUpdated | 在组件更新之后被调用,此时DOM已经更新 | onUpdated(() => { // 在组件更新后的逻辑 }) |
onRenderTracked | 在组件渲染时被调用,用于追踪渲染过程中的依赖关系 | onRenderTracked((event) => { // 在组件渲染时的逻辑 }) |
onRenderTriggered | 在组件渲染时被调用,用于追踪触发渲染的依赖关系 | onRenderTriggered((event) => { // 在组件渲染时的逻辑 }) |
选项式的生命周期钩子图:
测试代码
vue
<template>
<div>
<!-- 使用v-if组件会直接销毁,而v-show则是隐藏 -->
<life v-if="station"/>
<!-- 用来销毁life组件,使onBeforeUnmount和onUnmounted被调用 -->
<Button @click="station=!station" hoverF="销毁" hoverS="click!" />
</div>
</template>
<script setup lang='ts'>
import { ref } from 'vue';
import Button from '../../组件库/Button.vue'
import life from './life.vue'
const station=ref<boolean>(true)
</script>
<style scoped></style>
vue
<template>
<div>
<div ref="div" style="font-size: 20px;font-weight: 600;color: #ecc208;">{{ str }}</div>
<Button @click="change" hoverF="修改" hoverS="click!" />
</div>
</template>
<script setup lang='ts'>
import { ref, getCurrentInstance, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, onRenderTracked, onRenderTriggered } from 'vue';
import Button from '../../组件库/Button.vue'
// import { ref } from 'vue'
// 在使用options API时是没有beforeCreate和created这两个生命周期的
// setup语法糖模式是没有beforeCreate和created这两个生命周期的
console.log('setup')
const str = ref<string>('Rarrot')
const div = ref<HTMLDivElement>()
const change = () => {
str.value = "我被修改了"
}
// 通过getCurrentInstance查看当前组件的实例,也是一个hook
const instance=getCurrentInstance()
console.log("🚀 instance", instance)
// 组件第一次渲染前调用,此时根元素不存在,无法访问到DOM
onBeforeMount(() => {
console.log("组件第一次渲染前调用", div.value)//undefined
})
// 组件第一次渲染后调用,该元素现在可用,允许直接DOM访问
onMounted(() => {
console.log("组件第一次渲染后调用", div.value)//<div>Rarrot</div>
})
// 获取更新之前的dom
onBeforeUpdate(() => {
console.log("更新前")
})
// 获取更新之后的dom
onUpdated(() => {
console.log("更新后")
})
// 销毁组件之前调用
onBeforeUnmount(() => {
console.log("销毁组件之前调用")
})
// 销毁组件之后调用
onUnmounted(() => {
console.log("销毁组件之后调用")
})
// 以下两个钩子主要用于调试,onRenderTracked用来获取收集的依赖
onRenderTracked((e) => {
// 打印effect等对象
console.log(e)
})
// 依赖被修改时则会调用此钩子
onRenderTriggered((e) => {
console.log(e)
})
</script>
<style scoped></style>
测试用例
可以用F12打开 开发者工具 查看
Rarrot
源码解读
通过使用一个枚举类型将钩子挂载到组件实例上:
typescript
// 生命周期的钩子函数时在生命周期的各个阶段去执行,所以把这些钩子挂载到组件的实例上面
export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT)
export const onMounted = createHook(LifecycleHooks.MOUNTED)
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE)
export const onUpdated = createHook(LifecycleHooks.UPDATED)
export const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT)
export const onUnmounted = createHook(LifecycleHooks.UNMOUNTED)
export const onServerPrefetch = createHook(LifecycleHooks.SERVER_PREFETCH)
export type DebuggerHook = (e: DebuggerEvent) => void
export const onRenderTriggered = createHook<DebuggerHook>(
LifecycleHooks.RENDER_TRIGGERED
)
export const onRenderTracked = createHook<DebuggerHook>(
LifecycleHooks.RENDER_TRACKED
)
typescript
export const enum LifecycleHooks {
BEFORE_CREATE = 'bc',
CREATED = 'c',
BEFORE_MOUNT = 'bm',
MOUNTED = 'm',
BEFORE_UPDATE = 'bu',
UPDATED = 'u',
BEFORE_UNMOUNT = 'bum',
UNMOUNTED = 'um',
DEACTIVATED = 'da',
ACTIVATED = 'a',
RENDER_TRIGGERED = 'rtg',
RENDER_TRACKED = 'rtc',
ERROR_CAPTURED = 'ec',
SERVER_PREFETCH = 'sp'
}
可以通过getCurrentInstance
来查看该组件注册的实例
由于钩子函数传入的参数只有第一个参数(lifecycle)是不同的,所以使用createHook
实现一个科里化将其封装起来,最后调用injectHook()
,源码如下:
typescript
export const createHook =
<T extends Function = () => any> (lifecycle: LifecycleHooks) =>
(hook: T, target: ComponentInternalInstance | null = currentInstance) =>
// post-create lifecycle registrations are noops during SSR (except for serverPrefetch)
// 创建后生命周期注册在 SSR 期间是无操作的(serverPrefetch 除外)
(!isInSSRComponentSetup || lifecycle === LifecycleHooks.SERVER_PREFETCH) &&
injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)
用于注册钩子的函数injectHook()
:
展开代码
typescript
export function injectHook(
type: LifecycleHooks,
hook: Function & { __weh?: Function },
target: ComponentInternalInstance | null = currentInstance,
prepend: boolean = false
): Function | undefined {
if (target) {
// 如果有hook函数直接返回,否则创建一个空数组,hooks为Function类型
const hooks = target[type] || (target[type] = [])
// 缓存injected hooks的错误处理包装器,因此相同的hook可以由调度程序正确删除重复数据。
// “__weh”代表“有错误要处理”。
const wrappedHook =
hook.__weh ||
(hook.__weh = (...args: unknown[]) => {
if (target.isUnmounted) {
return
}
// disable tracking inside all lifecycle hooks
// since they can potentially be called inside effects.
// 停止依赖收集,避免重复收集依赖
pauseTracking()
// Set currentInstance during hook invocation.
// This assumes the hook does not synchronously trigger other hooks, which
// can only be false when the user does something really funky.
// 设置target为当前实例
setCurrentInstance(target)
const res = callWithAsyncErrorHandling(hook, target, type, args)
unsetCurrentInstance()//清空当前组件实例
resetTracking()//恢复依赖收集
return res
})
if (prepend) {
hooks.unshift(wrappedHook)
} else {
hooks.push(wrappedHook)
}
return wrappedHook
} else if (__DEV__) {
const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, ''))
warn(
`${apiName} is called when there is no active component instance to be ` +
`associated with. ` +
`Lifecycle injection APIs can only be used during execution of setup().` +
(__FEATURE_SUSPENSE__
? ` If you are using async setup(), make sure to register lifecycle ` +
`hooks before the first await statement.`
: ``)
)
}
}
具体对于dom的操作,每个阶段的调用的代码在源码的./runtime-core/renderer.ts中。