首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Vue.js技术揭秘-生命周期|组件注册

生命周期

源码中最终执行生命周期的函数都是调用 callHook 方法,它的定义在 src/core/instance/lifecycle 中:

callHook 函数的逻辑,根据传入的字符串 hook,去拿到 vm.$options[hook] 对应的回调函数数组,然后遍历执行,执行的时候把 vm 作为函数执行的上下文。

beforeCreate & created

beforeCreate 和 created 函数都是在实例化 Vue 的阶段,在 _init 方法中执行的,它的定义在 src/core/instance/init.js 中:

可以看到 beforeCreate 和 created 的钩子调用是在 initState 的前后,initState 的作用是初始化 props、data、methods、watch、computed 等属性,之后我们会详细分析。那么显然 beforeCreate 的钩子函数中就不能获取到 props、data 中定义的值,也不能调用 methods 中定义的函数。

在这俩个钩子函数执行的时候,并没有渲染 DOM,所以我们也不能够访问 DOM,一般来说,如果组件在加载的时候需要和后端有交互,放在这俩个钩子函数执行都可以,如果是需要访问 props、data 等数据的话,就需要使用 created 钩子函数。之后我们会介绍 vue-router 和 vuex 的时候会发现它们都混合了 beforeCreate 钩子函数。

beforeMount & mounted

beforeMount 钩子函数发生在 mount,也就是 DOM 挂载之前,它的调用时机是在 mountComponent 函数中,定义在 src/core/instance/lifecycle.js 中:

在执行 vm._render() 函数渲染 VNode 之前,执行了 beforeMount 钩子函数,在执行完 vm._update() 把 VNode patch 到真实 DOM 后,执行 mounted 钩子。注意,这里对 mounted 钩子函数执行有一个判断逻辑,vm.$vnode 如果为 null,则表明这不是一次组件的初始化过程,而是我们通过外部 new Vue 初始化过程。

之前我们提到过,组件的 VNode patch 到 DOM 后,会执行 invokeInsertHook 函数,把 insertedVnodeQueue 里保存的钩子函数依次执行一遍,它的定义在 src/core/vdom/patch.js 中:

该函数会执行 insert 这个钩子函数,对于组件而言,insert 钩子函数的定义在 src/core/vdom/create-component.js 中的 componentVNodeHooks 中:

我们可以看到,每个子组件都是在这个钩子函数中执行 mounted 钩子函数,并且我们之前分析过,insertedVnodeQueue 的添加顺序是先子后父,所以对于同步渲染的子组件而言,mounted 钩子函数的执行顺序也是先子后父。

beforeUpdate & updated

beforeUpdate 和 updated 的钩子函数执行时机都应该是在数据更新的时候,到目前为止,我们还没有分析 Vue 的数据双向绑定、更新相关。

beforeUpdate 的执行时机是在渲染 Watcher 的 before 函数中,我们刚才提到过:

注意这里有个判断,也就是在组件已经 mounted 之后,才会去调用这个钩子函数。

update 的执行时机是在flushSchedulerQueue 函数调用的时候,它的定义在 src/core/observer/scheduler.js 中:

flushSchedulerQueue 函数我们之后会详细介绍,可以先大概了解一下,updatedQueue 是更新了的 wathcer 数组,那么在 callUpdatedHooks 函数中,它对这些数组做遍历,只有满足当前 watcher 为 vm._watcher 以及组件已经 mounted 这两个条件,才会执行 updated 钩子函数。

在组件 mount 的过程中,会实例化一个渲染的 Watcher 去监听 vm 上的数据变化重新渲染,这段逻辑发生在 mountComponent 函数执行的时候:

那么在实例化 Watcher 的过程中,在它的构造函数里会判断 isRenderWatcher,接着把当前 watcher 的实例赋值给 vm._watcher,定义在 src/core/observer/watcher.js 中:

同时,还把当前 wathcer 实例 push 到 vm._watchers 中,vm._watcher 是专门用来监听 vm 上数据变化然后重新渲染的,所以它是一个渲染相关的 watcher,因此在 callUpdatedHooks 函数中,只有 vm._watcher 的回调执行完毕后,才会执行 updated 钩子函数。

beforeDestroy & destroyed

beforeDestroy 和 destroyed 钩子函数的执行时机在组件销毁的阶段,组件的销毁过程之后会详细介绍,最终会调用 $destroy 方法,它的定义在 src/core/instance/lifecycle.js 中:

beforeDestroy 钩子函数的执行时机是在$destroy函数执行最开始的地方,接着执行了一系列的销毁动作,包括从 parent 的 $children 中删掉自身,删除 watcher,当前渲染的 VNode 执行销毁钩子函数等,执行完毕后再调用 destroy 钩子函数。

在 $destroy 的执行过程中,它又会执行 vm.__patch__(vm._vnode, null) 触发它子组件的销毁钩子函数,这样一层层的递归调用,所以 destroy 钩子函数执行顺序是先子后父,和 mounted 过程一样。

组件注册

在 Vue.js 中,除了它内置的组件如 、、、 等,其它用户自定义组件在使用前必须注册。很多同学在开发过程中可能会遇到如下报错信息:

一般报这个错的原因都是我们使用了未注册的组件。Vue.js 提供了 2 种组件的注册方式,全局注册和局部注册。接下来我们从源码分析的角度来分析这两种注册方式。

全局注册

要注册一个全局组件,可以使用 。例如:

那么, 函数是在什么时候定义的呢,它的定义过程发生在最开始初始化 Vue 的全局函数的时候,代码在 中:

函数首先遍历 ,得到 后挂载到 Vue 上 。 的定义在 中:

所以实际上 Vue 是初始化了 3 个全局函数,并且如果 是 且 是一个对象的话,通过 , 相当于 把这个对象转换成一个继承于 Vue 的构造函数,最后通过 把它挂载到 上。

由于我们每个组件的创建都是通过 继承而来,我们之前分析过在继承的过程中有这么一段逻辑:

也就是说它会把 合并到 ,也就是组件的 上, 然后在组件的实例化阶段,会执行 逻辑,把 合并到 上。

然后在创建 的过程中,会执行 方法,我们再来回顾一下这部分的逻辑,它的定义在 中:

这里有一个判断逻辑 ,先来看一下 的定义,在 中:

这段逻辑很简单,先通过 拿到 ,然后再尝试拿 ,这里有个顺序,先直接使用 拿,如果不存在,则把 变成驼峰的形式再拿,如果仍然不存在则在驼峰的基础上把首字母再变成大写的形式再拿,如果仍然拿不到则报错。这样说明了我们在使用 全局注册组件的时候,id 可以是连字符、驼峰或首字母大写的形式。

那么回到我们的调用 ,即拿 ,这样我们就可以在 的时候拿到这个组件的构造函数,并作为 的钩子的参数。

局部注册

Vue.js 也同样支持局部注册,我们可以在一个组件内部使用 选项做组件的局部注册,例如:

其实理解了全局注册的过程,局部注册是非常简单的。在组件的 Vue 的实例化阶段有一个合并 的逻辑,之前我们也分析过,所以就把 合并到 上,这样我们就可以在 的时候拿到这个组件的构造函数,并作为 的钩子的参数。

注意,局部注册和全局注册不同的是,只有该类型的组件才可以访问局部注册的子组件,而全局注册是扩展到 下,所以在所有组件创建的过程中,都会从全局的 扩展到当前组件的 下,这就是全局注册的组件能被任意使用的原因。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20230113A03QD200?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券