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