Vue3源码阅读——组件创建及其初始化过程
前言
本文属于笔者Vue3源码阅读系列第二篇文章,上一篇文章Vue3源码阅读——初始化流程笔者很详细的写出了vue3初始化的过程。在上一篇文章中的mountComponent方法中,我们说到了它的主要逻辑:
- 调用
createComponentInstance创建组件实例。 - 调用
setupComponent设置组件的props、slots等。 - 调用
setupRenderEffect设置渲染的副作用方法。
本文的重点——组件创建及其初始化过程,就在前两个点的分析。

createComponentInstance创建组件实例
在Vue2中通过new Vue()的方式创建组件实例对象,到了Vue3,通过对象字面量的方式创建对象,这两种方式并无本质区别,目的都是为了在组件整个生命周期中维护组件的属性、状态数据、上下文环境。
源码在packages/runtime-core/src/component.ts中:



可以看到,在这个方法中设置了组件的root、emit、上下文对象。组件实例对象上有非常多的属性,不过也不用太过关注,只需要知道这个属性大致是干嘛的就可以了。接下来看setupComponent。
setupComponent
源码在packages/runtime-core/src/component.ts中:

- 初始化
props。 - 初始化
slots。 - 判断组件是否是有状态的组件,如果是则调用
setupStatefulComponent。
接着看下setupStatefulComponent的逻辑:

- 先校验一下组件名是否符合规范。
- 如果还有局部注册的组件,也会遍历校验一下组件名。
- 校验指令名。
然后:
- 创建渲染代理的属性访问缓存对象——
accessCache。 - 创建渲染上下文代理。(为什么要创建代理?在Vue2中,
data、props...选项中的数据,本来是存在this._data、this._props上,但是我们在render中却可以直接通过this.xxx来访问data、props...选项中的数据,这就是因为Vue在初始化时帮我们做了代理。同样的在Vue3也一样,因此这里对instance.ctx做了代理)。 - 如果有
setup hook就调用。
接着咱们分析下PublicInstanceProxyHandlers,来看看属性访问缓存对象的作用。
源码在packages/runtime-core/src/componentPublicInstance.ts中:

上面图中是get方法的一部分,其大致逻辑如下:
- 如果
key不以$开头,则获取accessCache[key],如果accessCache[key]不为undefined,根据accessCache[key]的值来判断该从setupState、data、ctx、props中的哪个对象取值返回; - 如果
accessCache[key]是undefined,那就依次判断这个key是否存在setupState、data、ctx、props中,存在则返回,否则一个一个的判断是否存在。 - 看到这里,我想各位应该看出
accessCache的作用到底是啥了 —— 由于Vue3将状态相关数据分类存放于setupState、data、ctx、props中,然后对这些对象做了代理,当访问this.xxx,就会调用get,由于事先并不知道这个xxx的key到底存在哪个数据对象中,因此不得不从setupState、data、ctx、props中按照先后顺序调用hasOwn判断key是否存在。如果每次访问都来这么判断,显然开销是巨大的。因此就有了accessCache,基于 空间换时间 的思想,当第一次找到了某个key存在哪个数据对象,就把这个key存在accessCache中,值就是包含这个key的数据对象,下一次再来访问这个key,就不需要再去判断hasOwn,直接从对应的数据对象中取值返回即可。
源码中也有注释说明了这一点:

接着看下get方法的另外一部分:

这个部分是用来处理key以$开头的情况:

来看下set的逻辑:

has的逻辑:

接下来我们看下setup hook调用:

逻辑如下:
- 判断有没有
setup hook,如果没有直接调用finishComponentSetup。 - 如果有,并且
setup是否有参数的话就调用createSetupContext创建,说白了就是在初始化setup hook执行的参数,比如我们可以像下面这样在setup hook中访问props、attrs、slots、emit、expose。

- 调用
setCurrentInstance,设置当前实例。 - 调用
pauseTracking,设置暂停收集依赖。 - 通过
callWithErrorHandling调用setup hook,毕竟setup hook是用户写的,只要是用户写的代码在框架内调用,都需要捕获未知异常,不能因为用户写的异常导致了框架的运行出错。
- 调用
resetTracking,重置shouldTrack。 - 调用
unsetCurrentinstance,重置当前实例。 - 处理
setup hook返回值是promise的情况。 - 如果
setup hook返回值不是promise,就调用handleSetupResult。
接下来咱们看下handleSetupResult:

在handleSetupResult中,如果setup hook返回的是一个函数,则把该函数当做render函数;如果是一个对象,需要判断该对象不是vnode,然后把这个对象代理一下并赋值给instance.setupState;如果不是对象抛出警告。最后调用了finishComponentSetup:


从finishComponentSetup的源码能够看出,finishComponentSetup主要的逻辑就是在标准化render和templdate、兼容OptionsAPI。
接下来看下packages/runtime-core/src/componentOptions.ts中applyOptions的,由于源码较长,此处不截图了,感兴趣的可以到源码中详细阅读。此处大致列出兼容OptionsAPI做了哪些事情:
- 合并,解析选项。
- 如果有
beforeCreate钩子,调用。 - 检查
data、methods、computed、watch、inject中的重复key。 - 处理
data。 - 处理
methods。 - 处理
props。 - 处理
inject。 - 处理
computed。 - 处理
watch。 - 处理
provide。 - 如果有
created钩子,调用。 - 注册生命周期钩子。
- 处理
expose。 - 处理
components。 - 处理
directives。 - 处理
filters。
到此组件创建及其初始化过程的结束了。
总结
本文主要内容大致如下:
- 组件实例的创建逻辑分析
- 设置组件实例
其中设置组件实例又详细描述了:
- 初始化
slots、props,这两部分的逻辑比较容易理解,可自行阅读 - 创建渲染上下文代理
- 上下文对象代理
key的缓存对象accessCache的作用及原理 - 渲染过程中访问状态数据的过程
setup hook的调用逻辑以及setup hook返回值的处理- 标准化
render、template - 兼容
OptionsAPI
这是笔者第二篇源码分析类的文章,如果掘友们有什么建议,或者文中有错误,还请评论指出,谢谢!
如果本文对你有一点点帮助,点个赞支持一下吧,你的每一个【赞】都是我创作的最大动力 ^_^。
转载自:https://juejin.cn/post/7244018340879155258