生命周期总览(四个阶段)
创建 -> 挂载 -> 更新 -> 卸载
常用的 7 个钩子(Composition API 名称):
setup()
(初始化阶段的入口)onBeforeMount
->onMounted
onBeforeUpdate
->onUpdated
onBeforeUnmount
->onUnmounted
辅助/特殊钩子:
- 错误边界:
onErrorCaptured
- KeepAlive:
onActivated
/onDeactivated
- 调试依赖跟踪(开发定位渲染原因):
onRenderTracked
/onRenderTriggered
- SSR:
onServerPrefetch
- 作用域销毁:
onScopeDispose
(与effectScope()
/ 组件外部的响应式作用域相关
调用时机
组件从创建到挂载:实例、setup、渲染副作用
- 创建组件实例:
createComponentInstance(vnode, parent)
- 位置:
runtime-core/src/component.ts
- 作用:为 VNode 创建 组件实例(
ComponentInternalInstance
),初始化一堆字段(type/props/slots/provides/appContext/...
),并给每种生命周期钩子预留数组存放位:
- 位置:
instance = {
// ...
// 这些数组就是注册生命周期钩子的“桶”
bm: null, // beforeMount hooks
m: null, // mounted hooks
bu: null, // beforeUpdate hooks
u: null, // updated hooks
bum: null, // beforeUnmount hooks
um: null, // unmounted hooks
a: null, // activated
da: null, // deactivated
ec: null, // errorCaptured
rtg: null, // renderTracked
rtc: null, // renderTriggered
// ...
}
- 运行组件初始化:
setupComponent(instance, isSSR)
- 位置:
component.ts
- 做两件事:
- 处理
props/slots
- 状态化组件:
setupStatefulComponent(instance)
- 处理
setupStatefulComponent(instance)
- 拉出
setup(props, ctx)
并执行(可能是async
):
setCurrentInstance(instance)
const setupResult = setup(shallowReadonly(instance.props), setupContext)
unsetCurrentInstance()
- 注册钩子就在
setup()
内部完成,此时只是把回调塞进 instance.xxx 的数组里;不会触发调用。 - 返回值处理:
- 若
setup
返回 函数:视作render
函数保存到instance.render
。 - 若返回 对象:挂到
instance.setupState
,渲染时通过proxy
暴露给模板。 - 若是 Promise:交给
Suspense
机制,挂载会延后。
- 若
- 建立渲染副作用:
setupRenderEffect(instance, initialVNode, container, anchor, ... )
- 位置:
renderer.ts
- 核心:创建一个 响应式副作用(
ReactiveEffect
),在其回调内 执行渲染/打补丁:
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
// 首次挂载 -> patch(subTree)
callHook(instance.bm) // onBeforeMount
subTree = renderComponentRoot(instance)
patch(null, subTree, container, anchor, instance)
instance.isMounted = true
queuePostRenderEffect(instance.m, parentSuspense) // onMounted (post-flush)
} else {
// 更新 -> 重新 render + patch
callHook(instance.bu) // onBeforeUpdate
const nextTree = renderComponentRoot(instance)
const prevTree = instance.subTree
instance.subTree = nextTree
patch(prevTree, nextTree, container, anchor, instance)
queuePostRenderEffect(instance.u, parentSuspense) // onUpdated (post-flush)
}
}, scheduler)
- 挂载/更新钩子调用点:
onBeforeMount
:首次patch
前onMounted
:patch
完后,通过队列延后(post-flush)onBeforeUpdate
:更新patch
前onUpdated
:更新patch
后,同样延后(post-flush)
钩子注册进实例:injectHook
- 位置:
apiLifecycle.ts
- 每个
onXXX
API 本质是调用injectHook(lifecycle, hook, targetInstance)
:
export function onMounted(hook, target = currentInstance) {
injectHook('m', hook, target) // 'm' 对应 instance.m
}
function injectHook(type, hook, target) {
const list = target[type] || (target[type] = [])
// 经过 error boundary 包装,保证抛错被上层捕获
const wrapped = (...args) => callWithAsyncErrorHandling(hook, target, type, args)
list.push(wrapped)
}
currentInstance
由setCurrentInstance
/unsetCurrentInstance
在执行setup()
时设置,保证在setup()
里调用onMounted(...)
能找到正确的组件实例。
父子先后顺序
- 首次挂载(先父后子,mounted 反过来)
渲染时流程(深度优先):
- patch(parent) → processComponent → mountComponent
- setupRenderEffect(parent) 执行 render 得到 subTree
- patch(null, subTree):这里会先递归挂载子组件
- 等子都挂好后,父的 onMounted 通过 post 队列 统一调用
因为 子组件的
onMounted
也走 post 队列,但它们被 更早推入队列(子先完成自身 patch),所以 子 mounted 先于父 mounted。
- 更新(父包子)
- 父更新时:
- 先 callHook(parent.bu)
- patch 过程中 触发子组件的更新 effect(所以子 bu/u 在父 u 之前)
- 最后队列里按入队顺序 先跑完子 updated,再跑父 updated
- 卸载(父宣布、子先执行)
unmountComponent(instance)
:
callHook(instance.bum) // beforeUnmount
// 递归卸载子树
unmount(subTree, instance) // 子先 unmount
queuePostRenderEffect(instance.um) // 最后父的 unmounted 入队
- 因为对子树先执行,所以观察到的顺序是 父 beforeUnmount → 子 beforeUnmount/子 unmounted → 父 unmounted。
Options API & Composition API 对照
意图/时机 | Options API(Vue 3 仍可用) | Composition API |
---|---|---|
初始化(早期逻辑) | beforeCreate / created * |
setup() |
首次挂载前/后 | beforeMount / mounted |
onBeforeMount / onMounted |
更新前/后 | beforeUpdate / updated |
onBeforeUpdate / onUpdated |
卸载前/后 | beforeDestroy - beforeUnmount / destroyed - unmounted |
onBeforeUnmount / onUnmounted |
错误捕获 | errorCaptured |
onErrorCaptured |
KeepAlive / Suspense / SSR
<KeepAlive>
- 被缓存的组件 不会卸载,因此:
- 不会再触发 onBeforeUnmount/onUnmounted
- 切换可见性时触发 onDeactivated(离开)与 onActivated(再次显示)
<Suspense>
与async setup
- 如果
setup
返回 Promise(或使用async setup
),渲染会等待其resolve
:
SSR
(服务端渲染)
- 只能在服务端使用的钩子:
onServerPrefetch
(可与客户端 hydration 对齐数据) - 客户端首次激活时仍会按正常顺序触发生命周期,但需注意避免重复取数(可用同构缓存、
useQuery
等状态层处理)。
watch / watchEffect 与生命周期的协作
- 默认
watch
/watchEffect
的回调是 渲染前(flush'pre'
)执行。 - 如果需要 DOM 更新后 再跑(比如读尺寸):
watch(..., { flush: 'post' })
。 - 清理副作用:
watchEffect((onCleanup) => {
const id = setInterval(doSomething, 1000)
onCleanup(() => clearInterval(id)) // 组件卸载或依赖变化时自动清理
})
时序流程图
首次挂载
render → patch(Component)
└─ processComponent → mountComponent
├─ createComponentInstance
├─ setupComponent
│ └─ setupStatefulComponent
│ └─ 执行 setup() → injectHook(注册 onXxx)
└─ setupRenderEffect
└─ effect():
├─ callHook(bm) // onBeforeMount
├─ subTree = render()
├─ patch(null, subTree) // 深度优先挂载子
└─ queuePostRenderEffect(m) // onMounted 入队
[flushJobs 尾部] → 依次执行所有 mounted(先子后父)
更新
state change → queueJob(update)
[flushJobs]
└─ update():
├─ callHook(bu) // onBeforeUpdate
├─ nextTree = render()
├─ patch(prev, next) // 递归更新子
└─ queuePostRenderEffect(u) // onUpdated 入队
[尾部] 执行 updated(先子后父)
卸载
unmountComponent
├─ callHook(bum) // onBeforeUnmount
├─ unmount(subTree) // 子先卸
└─ queuePostRenderEffect(um) // onUnmounted 父最后
[尾部] 执行 unmounted
常见时序: 子 mounted 先于父、updated 先子后父、KeepAlive 不触发 unmounted、改触发 activated/deactivated。
源码阅读流程
renderer.ts
:patch / processComponent / mountComponent / setupRenderEffect
component.ts
:createComponentInstance / setupComponent / setupStatefulComponent
apiLifecycle.ts
:onMounted/onUpdated/...
->injectHook
scheduler.ts
:queueJob / queuePostFlushCb / flushJobs
components/KeepAlive.ts
、components/Suspense.ts
手动实现一下生命周期
Mini-vue-lifecycle.js:
- 生命周期注册 API:
onBeforeMount/onMounted/onBeforeUpdate/onUpdated/onBeforeUnmount/onUnmounted
- 简易调度器(
queueJob/queuePostFlushCb/flushJobs
)与nextTick
- 组件实例化、
setup
、render
、setState
触发更新 - Parent/Child 示例验证”子 mounted 先于父” “updated 先子后父” “unmounted 顺序”等
// mini-vue-lifecycle.js
// 实现生命周期:
// onBeforeMount/onMounted/onBeforeUpdate/onUpdated/onBeforeUnmount/onUnmounted
// + 简易调度器(postFlush 队列)+ nextTick + setState 触发更新。
/******************* 调度器(scheduler) *******************/
const jobQueue = new Set();
const postFlushCbs = new Set();
let isFlushing = false;
const p = Promise.resolve();
function queueJob(job) {
jobQueue.add(job);
if (!isFlushing) flushJobs();
}
function queuePostFlushCb(cb) {
postFlushCbs.add(cb);
if (!isFlushing) flushJobs();
}
function flushJobs() {
isFlushing = true;
p.then(() => {
// 1) 执行渲染 job(可能在执行中新增 job,Set 去重)
jobQueue.forEach(job => job());
jobQueue.clear();
// 2) 执行 postFlush 回调(保证 DOM 已稳定)
postFlushCbs.forEach(cb => cb());
postFlushCbs.clear();
}).finally(() => {
isFlushing = false;
});
}
export function nextTick(fn) {
return fn ? p.then(fn) : p;
}
/******************* 生命周期 API 与实例结构 *******************/
let currentInstance = null;
function setCurrentInstance(i) { currentInstance = i; }
function unsetCurrentInstance() { currentInstance = null; }
function createHookRegistrar(key) {
return function register(hook) {
const i = currentInstance;
if (!i) throw new Error(`on${key} called without active instance`);
(i[key] || (i[key] = [])).push(hook);
};
}
export const onBeforeMount = createHookRegistrar('bm');
export const onMounted = createHookRegistrar('m');
export const onBeforeUpdate = createHookRegistrar('bu');
export const onUpdated = createHookRegistrar('u');
export const onBeforeUnmount = createHookRegistrar('bum');
export const onUnmounted = createHookRegistrar('um');
/******************* 组件实例与渲染 *******************/
function createComponentInstance(vnode, parent) {
return {
vnode,
type: vnode.type,
parent,
isMounted: false,
// 容纳生命周期函数的“桶”
bm: null, m: null, bu: null, u: null, bum: null, um: null,
// 状态 & 渲染
state: {},
render: null,
subTree: null,
el: null,
update: null,
};
}
function mountComponent(initialVNode, container, parent) {
const instance = (initialVNode.component = createComponentInstance(initialVNode, parent));
const Component = initialVNode.type;
// 执行 setup,允许在其中注册生命周期
setCurrentInstance(instance);
const setupResult = Component.setup ? Component.setup(Component.props || {}) : {};
unsetCurrentInstance();
// setup 返回对象作为 state;render 从组件取
instance.state = setupResult || {};
instance.render = Component.render; // render(ctx) => DOM Element
// 建立一次“渲染副作用”:区分首次挂载/后续更新
instance.update = function componentEffect() {
if (!instance.isMounted) {
// beforeMount
callHooks(instance.bm);
// 渲染
const el = instance.render(createRenderContext(instance));
instance.el = el;
container.appendChild(el);
instance.isMounted = true;
// mounted 延后(postFlush)
queuePostFlushCb(() => callHooks(instance.m));
} else {
// beforeUpdate
callHooks(instance.bu);
// 简化:重新渲染整棵节点(diff 省略)
const newEl = instance.render(createRenderContext(instance));
instance.el.replaceWith(newEl);
instance.el = newEl;
// updated 延后
queuePostFlushCb(() => callHooks(instance.u));
}
};
// 首次执行
queueJob(instance.update);
}
function unmountComponent(vnode) {
const instance = vnode.component;
if (!instance) return;
// beforeUnmount 先同步执行
callHooks(instance.bum);
// 移除 DOM(子树此处忽略递归以简洁)
if (instance.el && instance.el.parentNode) {
instance.el.parentNode.removeChild(instance.el);
}
// unmounted 延后
queuePostFlushCb(() => callHooks(instance.um));
}
function callHooks(list) {
if (Array.isArray(list)) list.forEach(fn => { try { fn(); } catch (e) { console.error(e); } });
}
function createRenderContext(instance) {
// 暴露给 render 的上下文:state + setState
return {
...instance.state,
setState(patch) {
const next = typeof patch === 'function' ? patch(instance.state) : { ...instance.state, ...patch };
instance.state = next;
queueJob(instance.update);
},
el: () => instance.el,
};
}
/******************* 运行时(应用入口) *******************/
export function h(type, props) { return { type, props, component: null }; }
export function createApp(Root) {
return {
mount(selectorOrEl) {
const container = typeof selectorOrEl === 'string' ? document.querySelector(selectorOrEl) : selectorOrEl;
if (!container) throw new Error('Container not found');
const vnode = h(Root, {});
mountComponent(vnode, container, null);
return {
unmount() { unmountComponent(vnode); },
_instance: vnode.component,
};
}
};
}
/******************* 父子组件与顺序验证 *******************/
export const Child = {
setup() {
onBeforeMount(() => console.log('%c[child] beforeMount','color:#999'));
onMounted(() => console.log('%c[child] mounted','color:#2c7'));
onBeforeUpdate(() => console.log('%c[child] beforeUpdate','color:#999'));
onUpdated(() => console.log('%c[child] updated','color:#2c7'));
onBeforeUnmount(() => console.log('%c[child] beforeUnmount','color:#999'));
onUnmounted(() => console.log('%c[child] unmounted','color:#2c7'));
return { count: 0 };
},
render(ctx) {
const el = document.createElement('div');
el.className = 'child';
el.textContent = `Child count = ${ctx.count}`;
return el;
}
};
export const Parent = {
setup() {
onBeforeMount(() => console.log('%c[parent] beforeMount','color:#999'));
onMounted(() => console.log('%c[parent] mounted','color:#2c7'));
onBeforeUpdate(() => console.log('%c[parent] beforeUpdate','color:#999'));
onUpdated(() => console.log('%c[parent] updated','color:#2c7'));
onBeforeUnmount(() => console.log('%c[parent] beforeUnmount','color:#999'));
onUnmounted(() => console.log('%c[parent] unmounted','color:#2c7'));
return { n: 0 };
},
render(ctx) {
const wrap = document.createElement('div');
wrap.className = 'parent';
const title = document.createElement('div');
title.textContent = `Parent n = ${ctx.n}`;
wrap.appendChild(title);
// 简易“子组件内嵌”演示:直接创建一个 Child 实例并挂在到 wrap
const childVNode = h(Child, {});
mountComponent(childVNode, wrap, null);
// 控制按钮
const btn = document.createElement('button');
btn.textContent = 'Parent setState(+1)';
btn.addEventListener('click', () => ctx.setState(s => ({ ...s, n: s.n + 1 })));
wrap.appendChild(btn);
return wrap;
}
};
使用方法:
import { createApp, Parent, nextTick } from './mini-vue-lifecycle.js'
const app = createApp(Parent).mount('#app')
// 触发一次更新:
app._instance.update(); // 或通过点击按钮
// 稍后:
// app.unmount();
HTML中:
<div id="app"></div>
<script type="module">
import { createApp, Parent } from '/path/mini-vue-lifecycle.js'
const app = createApp(Parent).mount('#app')
// 手动触发一次更新(也可点页面按钮):
app._instance.update()
// 卸载:
// app.unmount()
</script>
打开控制台,能看到打印顺序:
- 首次挂载:
[child] beforeMount
→... mounted
→[parent] mounted
(子先父后) - 更新:
beforeUpdate
/updated
(先子后父) - 卸载:
parent beforeUnmount
- 子beforeUnmount/unmounted
-parent unmounted