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


    概览

    借官网一张图充篇幅☺
    在这里插入图片描述
    这张图展示了一个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 */);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    下面我们就从挂载组件开始撸源码
    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行代码
            //~~~
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    先进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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    这里不多说,直接看finishComponentSetup

    function handleSetupResult(instance, setupResult, isSSR) {
    	//~~~
    	//此处省略n行代码
    	//~~~
        finishComponentSetup(instance, isSSR);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    再进到applyOptions

    function finishComponentSetup(instance, isSSR, skipOptions) {
    	//~~~
    	//此处省略n行代码
    	//~~~
    	//处理选项式api
    	applyOptions(instance);
    	//~~~
    	//此处省略n行代码
    	//~~~
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    重点来了,睁大你的眼睛
    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行代码
    	//~~~
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    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函数的代码,不做详细说明
    	//~~~
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

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

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

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

  • 相关阅读:
    硬件工程师秋招注意事项
    中期科技:智慧公厕打造智能化城市设施,提升公共厕所管理与服务体验
    【无标题】
    HTML5期末大作业【红色的电影售票平台网站】web前端 html+css+javascript网页设计实例 企业网站制作
    【Linux】安装 Docker Registry
    做题日记 之 Ananagrams(UVA-156)
    改变金融贷款市场营销方式 ---- 运营商大数据精准获客
    模板:斯坦纳树
    持续交付知易行难,想做成这事你要理解这几个关键点
    微信如何防止被限制?一文了解原因和处理方法
  • 原文地址:https://blog.csdn.net/BDawn/article/details/128022154