本文,我们将核心流程再过一遍,重点关注所有生命周期钩子函数的执行时机,以及它们在不同场景下的执行顺序。
1. 生命周期钩子函数的执行时机
Vue3 的生命周期主要分为四个阶段,每个阶段都有对应的生命周期钩子:
初始化阶段
- setup: 组件实例创建之前执行
- beforeCreate: 实例初始化之前 (在 Composition API 中等同于 setup 开始)
- created: 实例创建完成后 (在 Composition API 中等同于 setup 结束)
挂载阶段
- beforeMount: DOM 挂载之前
- mounted: DOM 挂载完成后
更新阶段
- beforeUpdate: 数据更新时,DOM 更新之前
- updated: DOM 更新完成后
卸载阶段
- beforeUnmount: 组件卸载之前
- unmounted: 组件卸载完成后
2. 组件初始化阶段
组件的生命周期从初始化开始。在这个阶段,Vue 会创建组件实例,设置响应式系统,并执行初始化相关的钩子函数。让我们详细看看这个阶段的实现:
2.1 setup
setup 是 Composition API 的入口,也是整个组件生命周期中最早执行的钩子。它在组件实例创建之前执行,这使得我们可以在这里进行一些预处理工作,比如创建响应式数据、注册生命周期钩子等。具体实现如下:
js
// 调用链:
// createApp -> mount -> render -> patch -> processComponent ->
// 在 setupComponent 函数中
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null);
setCurrentInstance(instance);
pauseTracking();
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
);
resetTracking();
unsetCurrentInstance();
}
实际应用示例:
js
// 在 setup 中初始化数据和方法
export default {
setup() {
// 1. 创建响应式数据
const count = ref(0);
const userInfo = reactive({
name: "",
age: 0,
});
// 2. 定义方法
const increment = () => count.value++;
// 3. 注册其他生命周期钩子
onMounted(() => {
// 组件挂载后的逻辑
});
return { count, userInfo, increment };
},
};
2.2 beforeCreate & created
在 Vue3 的 Composition API 中,这两个钩子实际上等同于 setup 函数:
- beforeCreate: 在 setup 开始时
- created: 在 setup 结束时
如果使用 Options API,这两个钩子会在组件实例初始化时执行:
js
// 调用链:
// createApp -> mount -> render -> patch -> processComponent ->
// mountComponent -> setupComponent -> setupStatefulComponent -> applyOptions
// componentOptions.ts
function applyOptions(instance: ComponentInternalInstance) {
const options = resolveMergedOptions(instance);
const publicThis = instance.proxy! as any;
const ctx = instance.ctx;
// 1. 处理生命周期钩子
if (options.beforeCreate) {
callHook(options.beforeCreate, instance, LifecycleHooks.BEFORE_CREATE);
}
// ... 其他选项的处理
if (options.created) {
callHook(options.created, instance, LifecycleHooks.CREATED);
}
}
// 生命周期钩子的枚举定义
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",
}
// 钩子函数的调用实现
function callHook(
hook: Function,
instance: ComponentInternalInstance,
type: LifecycleHooks
) {
callWithAsyncErrorHandling(hook.bind(instance.proxy), instance, type);
}
钩子函数的执行过程:
-
beforeCreate:
- 在 applyOptions 开始时执行
- 此时组件实例已创建,但还未进行其他选项的处理
- 不能访问 data、computed 等选项
-
created:
- 在其他选项处理完成后执行
- 此时已完成响应式数据、计算属性等的初始化
- 可以访问组件的所有选项
在 Composition API 中,这两个钩子的功能被 setup 函数取代,因为:
- setup 函数提供了更灵活的组合式 API
- 可以更好地组织和复用逻辑
- 提供了更好的类型推导
实际应用示例:
js
export default {
// Options API
beforeCreate() {
// 这里不能访问 data、computed 等
console.log("组件实例还未完全创建");
},
created() {
// 1. 可以访问响应式数据
console.log(this.someData);
// 2. 发起初始化数据请求
this.fetchInitialData();
// 3. 添加全局事件监听
window.addEventListener("resize", this.handleResize);
},
};
3. 挂载和更新阶段
当组件实例创建完成后,Vue 会进入挂载和更新阶段。这两个阶段的生命周期钩子都在 setupRenderEffect 函数中实现:
3.1 beforeMount
在 setupRenderEffect 函数中,组件挂载前会执行:
js
// createAppAPI -> mount -> render -> patch -> processComponent ->
// mountComponent -> setupComponent -> setupRenderEffect
const setupRenderEffect = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
namespace,
optimized
) => {
const componentUpdateFn = () => {
if (!instance.isMounted) {
// 挂载阶段
// 1. 执行 beforeMount 钩子
const { bm } = instance;
if (bm) {
invokeArrayFns(bm);
}
// 2. 渲染组件
const subTree = (instance.subTree = renderComponentRoot(instance));
// 3. 挂载子树
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
namespace
);
instance.isMounted = true;
// 4. 执行 mounted 钩子(异步)
const { m } = instance;
if (m) {
queuePostRenderEffect(m, parentSuspense);
}
} else {
// 更新阶段
// 1. 执行 beforeUpdate 钩子
const { bu } = instance;
if (bu) {
invokeArrayFns(bu);
}
// 2. 更新组件
const nextTree = renderComponentRoot(instance);
const prevTree = instance.subTree;
instance.subTree = nextTree;
// 3. 更新 DOM
patch(
prevTree,
nextTree,
container,
anchor,
instance,
parentSuspense,
namespace
);
// 4. 执行 updated 钩子(异步)
const { u } = instance;
if (u) {
queuePostRenderEffect(u, parentSuspense);
}
}
};
// 创建渲染副作用
instance.update = effect(componentUpdateFn, {
scheduler: () => queueJob(instance.update),
});
};
特点:
- 同步钩子(beforeMount/beforeUpdate)在 DOM 操作前执行,可以访问更新前的状态
- 异步钩子(mounted/updated)通过 queuePostRenderEffect 调度,确保在 DOM 更新后执行
- 整个过程由 effect 包装,响应式数据变化时自动触发更新流程
实际应用示例:
js
export default {
setup() {
onBeforeMount(() => {
// 1. DOM 渲染前的准备工作
initializeThirdPartyLib();
});
onMounted(() => {
// 1. 访问或操作 DOM
const element = document.getElementById("my-element");
// 2. 初始化第三方库
new ThirdPartyChart(element);
// 3. 发起数据请求
fetchData();
});
onBeforeUpdate(() => {
// 1. 更新前保存一些状态
saveScrollPosition();
});
onUpdated(() => {
// 1. DOM 更新后恢复状态
restoreScrollPosition();
// 2. 更新第三方库的视图
updateChartView();
});
},
};
4. 卸载阶段
当组件被卸载时(比如通过 v-if 移除组件或手动调用 unmount),Vue 会执行一系列清理工作。让我们看看卸载阶段的生命周期钩子实现:
js
// unmount 调用链:
// 1. 组件被移除 -> render(null, container)
// 2. patch(prevVNode, null, ...)
// 3. unmount(prevVNode, ...)
// 4. unmountComponent(...)
const unmountComponent = (
instance: ComponentInternalInstance,
parentSuspense: SuspenseBoundary | null,
doRemove?: boolean
) => {
const { bum, scope, update, subTree, um } = instance;
// 1. 执行 beforeUnmount 钩子
if (bum) {
invokeArrayFns(bum);
}
// 2. 停止响应式副作用
scope.stop();
if (update) {
update.active = false;
}
// 3. 卸载子树
unmount(subTree, instance, parentSuspense, doRemove);
// 4. 执行 unmounted 钩子(异步)
if (um) {
queuePostRenderEffect(um, parentSuspense);
}
};
特点:
- 卸载过程是自上而下的,先处理父组件,再处理子组件
- beforeUnmount 是同步执行的,确保在 DOM 移除前有机会进行清理
- unmounted 是异步执行的,确保所有子组件都完成卸载
- 完整的清理机制确保不会产生内存泄漏
实际应用示例:
js
export default {
setup() {
// 1. 创建需要清理的资源
const timer = setInterval(() => {
// 定时任务
}, 1000);
const unsubscribe = store.subscribe(() => {
// 状态订阅
});
onBeforeUnmount(() => {
// 1. 清除定时器
clearInterval(timer);
// 2. 取消状态订阅
unsubscribe();
// 3. 移除事件监听
window.removeEventListener("resize", handleResize);
});
onUnmounted(() => {
// 1. 销毁第三方库实例
chart.destroy();
// 2. 清理缓存数据
localStorage.removeItem("tempData");
});
},
};
4. 总结
Vue3 的生命周期钩子执行顺序如下:
初始化阶段
- setup (最早执行)
- beforeCreate (等同于 setup 开始)
- created (等同于 setup 结束)
首次挂载
- beforeMount (同步)
- mounted (异步)
更新过程
- beforeUpdate (同步)
- updated (异步)
卸载过程
- beforeUnmount (同步)
- unmounted (异步)
特点:
- 每个阶段都有组件级和 vnode 级的钩子函数
- beforeXXX 钩子都是同步执行的
- 其他钩子都是通过 queuePostRenderEffect 异步执行的
- 钩子函数的执行顺序是确定的,有助于我们控制代码逻辑
- setup 是一个特殊的钩子,它在组件实例创建之前执行
- Composition API 中 beforeCreate 和 created 被 setup 替代
- 特殊钩子针对特定场景提供了额外的生命周期控制
- 开发环境提供了更多的调试钩子
这种设计让我们能够在组件生命周期的不同阶段执行需要的逻辑,同时通过同步/异步执行的区分,保证了性能和正确性。