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

news2024/11/24 8:52:09

概览

借官网一张图充篇幅☺
在这里插入图片描述
这张图展示了一个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行代码
	//~~~
	//处理选项式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在组件第一次渲染不会执行,只有组件变为不活动状态然后再变为活动状态时才会执行

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/58411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

函数的节流和防抖?节流和防抖的区别及实现

一.防抖和节流的本质 本质上就是优化高频率执行代码的一种手段。 比如说:浏览器的scroll,keypress,mousemove,resize等事件在触发时,会不断的调用绑定在事件上的回调函数,会极大地浪费资源,降低前端的性能。 因此为了优化用户体…

【C++模板】非类型模板参数

目录什么是非类型模板参数?非类型的类模板参数非类型的函数模板参数非类型模板参数的局限性限制使用的场景支持使用的场景什么是非类型模板参数? 在函数模板和类模板中,模板参数并不仅仅可以当作类型,还可以当作普通值。当使用普通…

Numpy解决找出二维随机矩阵中每行数据中最接近某个数字的数字

解决思路: 利用np.random.rand()函数生成随机的矩阵。abs函数实现对矩阵中每一个元素和指定元素相减np.argsort()函数实现找到排序后新元素在原来矩阵中的下标利用mask函数提取矩阵中第一列的元素最后利用for循环遍历所有的二维坐标,找到矩阵中每行中满…

微信小程序-读取数据

在开发微信小程序的时候,我们经常都会用到一些配置数据,或者当做“单向数据库(只读)”使用。 我们新建一个新的项目工程,JS版本就可以。 免于麻烦,我们新建一个page(showdata)来显示数据。 为了方便管理,我们在项目工程新建一个目录(data),用于存数据。另外我们再新…

面向对象-05-06-构造方法,标准的 javabean 类

实例化的本质就是调用构造方法 package com.luo.demo01;public class StudenTest {public static void main(String[] args) {// 创建对象// 本质:调用构造器Student s new Student();Student student new Student("luo",18);System.out.println(studen…

Git系列,自定义 git 命令,用 shell 脚本帮助你更好的实现 git 版本控制

一、问题引出 在实际的生产当中,无论是 git、小乌龟 git 、idea git 插件,都满足不了我们生产中遇到的一些常见的问题,例如: 工作任务重的时候,手头上可能有若干个分支,每个分支对应着不同的业务&#xf…

Mysql面试题汇总

Mysql面试题 文章目录Mysql面试题一 Mysql索引001 Mysql如何实现的索引机制?002 InnoDB索引与MyISAM索引实现的区别是什么?003 一个表中如果没有创建索引,那么还会创建B树吗?004 说一下B树索引实现原理(数据结构&#…

妙啊,Python 管道 Pipe 编写代码如此优雅

大家好,今天这篇文章我将详细讲解 Pipe 如何让你的代码更加简洁的方法,喜欢本文点赞支持,欢迎收藏学习,文末提供技术交流群,欢迎畅聊! 我们知道 map 和 filter 是两种有效的 Python 方法来处理可迭代对象。…

如何基于YAML设计接口自动化测试框架?看完秒会!

在设计自动化测试框架的时候,我们会经常将测试数据保存在外部的文件(如Excel、YAML、CSV)或者数据库中,实现脚本与数据解耦,方便后期维护。目前非常多的自动化测试框架采用通过Excel或者YAML文件直接编写测试用例&…

[附源码]计算机毕业设计springboot招聘系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

电子学会2021年6月青少年软件编程(图形化)等级考试试卷(四级)答案解析

青少年软件编程(图形化)等级考试试卷(四级) 分数:100.00 题数:24 一、单选题(共10题,每题3分,共30分) 1. 执行下列程序,输出的结果为…

bert 环境搭建之PytorchTransformer 安装

这两天跑以前的bert项目发现突然跑不了,报错信息如下: Step1 transformer 安装 RuntimeError: Failed to import transformers.models.bert.modeling_bert because of the following error (look up to see its traceback): module signal has no att…

IOT物联网系统架构

主要由 IotCloodServer物网联服务平台, IotAdminWeb物联网管理平台, IotDataProcessing物联网数据平台, IotDeviceGateway物联网边缘网关, IotDeviceToolHepler物联网边缘网关 控制 设备的工具类, IotApp物联网应…

SpringBoot_整合Mybatis

一 导入依赖 <!--整合mybatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.3</version></dependency><dependency><group…

ubuntu20.04屏幕亮度无法调节的解决方法->安装 brightness-controller-simple 软件

文章目录一、问题描述二、解决方法参考链接一、问题描述 安装ubunt20.04.5 之后&#xff0c;调节Ubuntu上方的亮度控制条、按快捷键(FnF5、FnF6) 都不能实现调节亮度的功能。 二、解决方法 安装 brightness-controller-simple 软件&#xff0c;利用软件调节亮度。 sudo add…

论文投稿指南——中文核心期刊推荐(计算机技术2)

>>>深度学习Tricks&#xff0c;第一时间送达<<< 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊。下面&#xff0c;简单介绍下什么是中文核心期刊要目总览&#xff1a; 《中文核心期刊要目总…

Web安全专业学习路线

最专业、全面的网络安全学习路线来咯~&#xff08;虽然是网络安全学习路线&#xff0c;但重心还是在Web安全上&#xff09; 展示学习路线之前&#xff0c;建议大家先了解一下这几个问题&#xff0c;既是认清形势&#xff0c;也是认清自我&#xff1a; 为什么要学网络安全&…

期末测试——JavaScript方式练习题

练习目标&#xff1a; 技术简介&#xff1a; js外部引入顺序结构jQuery Dom操作JavaScrip循环技巧JavaScrip数据操作资源地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1VZMGTKj3Aq9Zn6mtee0egw 提取码&#xff1a;1111 关键字&#xff1a; 1、append()&#x…

高分辨率遥感卫星影像在交通方面的应用及高分二号影像获取

高分辨率遥感影像在城市交通领域具有广泛的应用前景&#xff1a;如遥感交通调查、遥感影像地图与电子地图制作、道路工程地质遥感解译、交通安全与知道抗灾救灾、交通事故现场快速勘察、交通需求预测、车辆与车牌视频识别等等。高分辨率影像比如高分二号卫星、高分一号卫星&…

基于蝙蝠算法实现电力系统经济调度(Matlab代码实现)

目录 摘要&#xff1a; 1.蝙蝠优化算法的基本原理&#xff1a; 2.蝙蝠优化算法的流程&#xff1a; 3.仿真实验分析&#xff1a; 摘要&#xff1a; 基于Matalb平台&#xff0c;构建基于蝙蝠活动行为的蝙蝠优化算法&#xff0c;对一个含有6个火电机组的电力系统进行优化调度…