从源码分析vue3组件的生命周期
创始人
2024-03-15 18:14:10

概览

借官网一张图充篇幅☺
在这里插入图片描述
这张图展示了一个vue组件从开始渲染到卸载结束一整个生命周期经历的每个环节
但只罗列了选项式api生命周期钩子,没有将组合式api的生命周期钩子放进去
下面这个表格列出了所有选项式api生命周期钩子和组合式api生命周期钩子,以及他们的对应关系和执行的时机

组合式api选项式api执行时机
beforeCreate初始化组件内的属性(如:datapropswatchcomputed等)之前
created初始化组件内的属性(如:datapropswatchcomputed等)之后
onBeforeMount()beforeMount组件开始挂载之前
onMounted()mounted组件挂载之后
onBeforeUpdate()beforeUpdate组件数据更新之后,页面更新之前
onUpdated()updated组件数据更新之后,页面更新之后
onBeforeUnmount()beforeUnmount组件即将卸载,但还未卸载
onUnmounted()unmounted组件卸载之后
onErrorCaptured()errorCaptured捕获了后代组件传递的错误时
onRenderTracked()renderTracked响应式依赖被组件的渲染作用追踪后,仅开发模式下使用
onRenderTriggered()renderTriggered响应式依赖被组件触发了重新渲染之后,仅开发模式下使用
onActivated()activated组件被keep-alive包裹,页面从不活动状态变为活动状态执时
onDeactivated()deactivated组件被keep-alive包裹,页面从活动状态变为不活动状态执时
onServerPrefetch()serverPrefetch组件实例在服务器上被渲染之前,为异步函数,仅ssr模式使用

源码分析

由于源码过多,贴源码的时候会省略无关代码
代码里面的英文注释为源码注释,中文注释为笔者所写
准备好开始撸源码了吗😏

我们先看一下vue3是如何处注册生命周期钩子函数的
vue3直接通过类型声明了所有组合式生命周期api,当我们调用这些函数的时候vue会通过类型创建相应的生命周期钩子函数,这个很重要,不但我们实际开发式会这么做,vue也会通过这种方式去处理我们在组件内部定义的生命周期相关的选项式函数,在分析后面源码时会提到。

const onBeforeMount = createHook("bm" /* LifecycleHooks.BEFORE_MOUNT */);
const onMounted = createHook("m" /* LifecycleHooks.MOUNTED */);
const onBeforeUpdate = createHook("bu" /* LifecycleHooks.BEFORE_UPDATE */);
const onUpdated = createHook("u" /* LifecycleHooks.UPDATED */);
const onBeforeUnmount = createHook("bum" /* LifecycleHooks.BEFORE_UNMOUNT */);
const onUnmounted = createHook("um" /* LifecycleHooks.UNMOUNTED */);
const onServerPrefetch = createHook("sp" /* LifecycleHooks.SERVER_PREFETCH */);
const onRenderTriggered = createHook("rtg" /* LifecycleHooks.RENDER_TRIGGERED */);
const onRenderTracked = createHook("rtc" /* LifecycleHooks.RENDER_TRACKED */);

下面我们就从挂载组件开始撸源码
mountComponent是挂载组件的入口,里面包含了两个分支函数setupComponentsetupRenderEffect
看这两个函数名字我们大概知道他们是干嘛的

  • setupComponent:安装组件,主要来初始化定义组件时我们在组件内部定义的所有属性
  • setupRenderEffect:安装渲染特效,那肯定是将定义组件时的模板渲染为我们可以看到的页面内容了
const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense));//~~~//此处省略n行代码//~~~// resolve props and slots for setup context{{startMeasure(instance, `init`);}setupComponent(instance);{endMeasure(instance, `init`);}}//~~~//此处省略n行代码//~~~setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);//~~~//此处省略n行代码//~~~}

先进setupComponent,重点看一下setupStatefulComponent这个函数

function setupComponent(instance, isSSR = false) {isInSSRComponentSetup = isSSR;const { props, children } = instance.vnode;const isStateful = isStatefulComponent(instance);initProps(instance, props, isStateful, isSSR);initSlots(instance, children);const setupResult = isStateful? setupStatefulComponent(instance, isSSR): undefined;isInSSRComponentSetup = false;return setupResult;
}

setupStatefulComponent这个函数主要什么工作呢?

  • 首先执行了我们定义组件时的setup函数,当然也包括在setup里面编写的所有生命周期相关的组合式api代码
  • 然后处理setup返回的结果
function setupStatefulComponent(instance, isSSR) {//~~~//此处省略n行代码//~~~// 2. call setup() 执行setup函数const { setup } = Component;if (setup) {//~~~//此处省略n行代码//~~~// 调用setup并获得返回的结果const setupResult = callWithErrorHandling(setup, instance, 0 /* ErrorCodes.SETUP_FUNCTION */, [reactivity.shallowReadonly(instance.props) , setupContext]);reactivity.resetTracking();unsetCurrentInstance();if (shared.isPromise(setupResult)) {//~~~//此处省略n行代码//~~~}else {handleSetupResult(instance, setupResult, isSSR);}}else {finishComponentSetup(instance, isSSR);}
}

这里不多说,直接看finishComponentSetup

function handleSetupResult(instance, setupResult, isSSR) {//~~~//此处省略n行代码//~~~finishComponentSetup(instance, isSSR);
}

再进到applyOptions

function finishComponentSetup(instance, isSSR, skipOptions) {//~~~//此处省略n行代码//~~~//处理选项式apiapplyOptions(instance);//~~~//此处省略n行代码//~~~}

重点来了,睁大你的眼睛
applyOptions这个函数的主要工作:

  • 执行beforeCreate钩子函数
  • 初始化初始化组件属性
  • 执行created钩子函数
  • 将选项式生命周期钩子函数注册为组合式生命周期钩子函数
    例如我们在组件内部定义了mounted函数,这个函数实际上会调用组合式api onMountedmounted函数注册为选项式钩子函数
function applyOptions(instance) {//~~~//此处省略n行代码//~~~// 在开始初始化组件属性之前调用了beforeCreate钩子函数// call beforeCreate first before accessing other options since// the hook may mutate resolved options (#2791)if (options.beforeCreate) {callHook(options.beforeCreate, instance, "bc" /* LifecycleHooks.BEFORE_CREATE */);}// 解构获取到组件实实例中的属性const { // state 状态属性data: dataOptions, computed: computedOptions, methods, watch: watchOptions, provide: provideOptions, inject: injectOptions, // lifecycle 生命周期钩子created, beforeMount, mounted, beforeUpdate, updated, activated, deactivated, beforeDestroy, beforeUnmount, destroyed, unmounted, render, renderTracked, renderTriggered, errorCaptured, serverPrefetch, // public API 公开apiexpose, inheritAttrs, // assets 资源属性components, directives, filters } = options;//~~~//此处省略n行代码//~~~// 这里源码注释说明了选项是属性的初始化顺序,建议拿起小笔记记一下// options initialization order (to be consistent with Vue 2):// - props (already done outside of this function)// - inject// - methods// - data (deferred since it relies on `this` access)// - computed// - watch (deferred since it relies on `this` access)//~~~//此处省略n行代码 此处省略的代码为初始化组件属性的代码//~~~//初始化完组件属性之后,调用的生命周期的created钩子函数if (created) {callHook(created, instance, "c" /* LifecycleHooks.CREATED */);}// 此处定义了一个将选项式生命周期钩子函数注册为组合式生命周期钩子函数的函数function registerLifecycleHook(register, hook) {if (shared.isArray(hook)) {hook.forEach(_hook => register(_hook.bind(publicThis)));}else if (hook) {register(hook.bind(publicThis));}}registerLifecycleHook(onBeforeMount, beforeMount);registerLifecycleHook(onMounted, mounted);registerLifecycleHook(onBeforeUpdate, beforeUpdate);registerLifecycleHook(onUpdated, updated);registerLifecycleHook(onActivated, activated);registerLifecycleHook(onDeactivated, deactivated);registerLifecycleHook(onErrorCaptured, errorCaptured);registerLifecycleHook(onRenderTracked, renderTracked);registerLifecycleHook(onRenderTriggered, renderTriggered);registerLifecycleHook(onBeforeUnmount, beforeUnmount);registerLifecycleHook(onUnmounted, unmounted);registerLifecycleHook(onServerPrefetch, serverPrefetch);//~~~//此处省略n行代码//~~~
}

ok,初始化完组件属性,下面就是渲染页面

setupRenderEffect这个函数主要是渲染页面内容

const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {// 里面定义了一个更新组件的函数const componentUpdateFn = () => {// 判断组件是否以及渲染过if (!instance.isMounted) {// 组件第一次渲染//~~~//此处省略n行代码 //~~~// 调用beforeMount生命周期钩子函数// onVnodeBeforeMountif (!isAsyncWrapperVNode &&(vnodeHook = props && props.onVnodeBeforeMount)) {invokeVNodeHook(vnodeHook, parent, initialVNode);}//~~~//此处省略n行代码 此处省略的代码为的页面渲染过程//~~~// 此处执行mounted生命周期钩子函数// mounted hookif (m) {queuePostRenderEffect(m, parentSuspense);}// 此处执行虚拟节点的mounted生命周期钩子函数// onVnodeMountedif (!isAsyncWrapperVNode &&(vnodeHook = props && props.onVnodeMounted)) {const scopedInitialVNode = initialVNode;queuePostRenderEffect(() => invokeVNodeHook(vnodeHook, parent, scopedInitialVNode), parentSuspense);}// 如果组件被keep-alive包裹,会执行activated生命周期钩子函数// activated hook for keep-alive roots.// #1742 activated hook must be accessed after first render// since the hook may be injected by a child keep-aliveif (initialVNode.shapeFlag & 256 /* ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE */ ||(parent &&isAsyncWrapper(parent.vnode) &&parent.vnode.shapeFlag & 256 /* ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE */)) {instance.a && queuePostRenderEffect(instance.a, parentSuspense);}//~~~//此处省略n行代码 此处省略的代码为的页面渲染过程//~~~}else {// 组件已经渲染,当响应式数据变化时会执行这里代码//~~~//此处省略n行代码//~~~// 此处执行beforeUpdate生命周期钩子函数// beforeUpdate hookif (bu) {shared.invokeArrayFns(bu);}//~~~//此处省略n行代码//~~~// 此处执行updated生命周期钩子函数// updated hookif (u) {queuePostRenderEffect(u, parentSuspense);}//~~~//此处省略n行代码//~~~}};//~~~//此处省略n行代码 此处省略的代码为如何触发componentUpdateFn函数的代码,不做详细说明//~~~
};

到此,组件从开始挂载到挂载成功期间跟生命周期钩子相关的代码已基本分析完毕。
下面我们对其中的部分要点做个总结:

  • 开发过程中通过组合式生命周期api注册的钩子函数要比通过选项式api定义的钩子函数执行的早
    也就是说如果通过onMounted注册一个钩子函数,它会直接在组件里定义mounted函数执行的早
  • vue的状态选项初始化顺序(有先到后)为props inject methods data computed watch
  • activated在组件第一次渲染不会执行,只有组件变为不活动状态然后再变为活动状态时才会执行

如果大家还有什么疑问,可以在评论区留言,相互学习!

相关内容

热门资讯

应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...