热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

从源码分析vue3组件的生命周期

概览借官网一张图充篇幅☺这张图展示了一个vue组件从开始渲染到卸载结束一整个生命周期经历的每个环节但只罗列了选项式api生命周期钩子,没有将组合式api的生命周期钩




概览

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


组合式api选项式api执行时机
beforeCreate初始化组件内的属性(如:data,props,watch,computed等)之前
created初始化组件内的属性(如:data,props,watch,computed等)之后
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行代码
//~~~
//处理选项式api
applyOptions(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 公开api
expose, 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生命周期钩子函数
// onVnodeBeforeMount
if (!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeBeforeMount)) {
invokeVNodeHook(vnodeHook, parent, initialVNode);
}
//~~~
//此处省略n行代码 此处省略的代码为的页面渲染过程
//~~~
// 此处执行mounted生命周期钩子函数
// mounted hook
if (m) {
queuePostRenderEffect(m, parentSuspense);
}
// 此处执行虚拟节点的mounted生命周期钩子函数
// onVnodeMounted
if (!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-alive
if (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 hook
if (bu) {
shared.invokeArrayFns(bu);
}
//~~~
//此处省略n行代码
//~~~
// 此处执行updated生命周期钩子函数
// updated hook
if (u) {
queuePostRenderEffect(u, parentSuspense);
}
//~~~
//此处省略n行代码
//~~~
}
};
//~~~
//此处省略n行代码 此处省略的代码为如何触发componentUpdateFn函数的代码,不做详细说明
//~~~
};

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


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

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







推荐阅读
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 本文是一篇翻译文章,介绍了async/await的用法和特点。async关键字被放置在函数前面,意味着该函数总是返回一个promise。文章还提到了可以显式返回一个promise的方法。该特性使得async/await更易于理解和使用。本文还提到了一些可能的错误,并希望读者能够指正。 ... [详细]
  • Imdevelopinganappwhichneedstogetmusicfilebystreamingforplayinglive.我正在开发一个应用程序,需要通过流 ... [详细]
  • css div中文字位置_超赞的 CSS 阴影技巧与细节
    本文的题目是CSS阴影技巧与细节。CSS阴影,却不一定是box-shadow与filter:drop-shadow,为啥?因为使用其他属性 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 一、路由首先需要配置路由,就是点击good组件进入goodDetail组件配置路由如下{path:goodDetail,component:goodDetail}同时在good组件中写入如下点击事件,路由中加入 ... [详细]
  • STM32 IO口模拟串口通讯
    转自:http:ziye334.blog.163.comblogstatic224306191201452833850647前阵子,调项目时需要用到低波 ... [详细]
  • 使用这个技巧要达到的目标:一般来说,模型和控制器你都不会有相同的类名字。让我先创建一个取名为post的model。classPostextendsModel{}现在 ... [详细]
  • x86 linux的进程调度,x86体系结构下Linux2.6.26的进程调度和切换
    进程调度相关数据结构task_structtask_struct是进程在内核中对应的数据结构,它标识了进程的状态等各项信息。其中有一项thread_struct结构的 ... [详细]
author-avatar
呵呵
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有