likes
comments
collection
share

Vue3源码阅读——组件创建及其初始化过程

作者站长头像
站长
· 阅读数 50

前言

本文属于笔者Vue3源码阅读系列第二篇文章,上一篇文章Vue3源码阅读——初始化流程笔者很详细的写出了vue3初始化的过程。在上一篇文章中的mountComponent方法中,我们说到了它的主要逻辑:

  1. 调用createComponentInstance创建组件实例。
  2. 调用setupComponent设置组件的propsslots等。
  3. 调用setupRenderEffect设置渲染的副作用方法。

本文的重点——组件创建及其初始化过程,就在前两个点的分析。

Vue3源码阅读——组件创建及其初始化过程

createComponentInstance创建组件实例

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

Vue3源码阅读——组件创建及其初始化过程

Vue3源码阅读——组件创建及其初始化过程

Vue3源码阅读——组件创建及其初始化过程

可以看到,在这个方法中设置了组件的rootemit、上下文对象。组件实例对象上有非常多的属性,不过也不用太过关注,只需要知道这个属性大致是干嘛的就可以了。接下来看setupComponent

setupComponent

源码在packages/runtime-core/src/component.ts中:

Vue3源码阅读——组件创建及其初始化过程

逻辑很清晰:
  1. 初始化props
  2. 初始化slots
  3. 判断组件是否是有状态的组件,如果是则调用setupStatefulComponent

接着看下setupStatefulComponent的逻辑:

Vue3源码阅读——组件创建及其初始化过程

如果是开发模式:
  1. 先校验一下组件名是否符合规范。
  2. 如果还有局部注册的组件,也会遍历校验一下组件名。
  3. 校验指令名。

然后:

  1. 创建渲染代理的属性访问缓存对象——accessCache
  2. 创建渲染上下文代理。(为什么要创建代理?在Vue2中,data、props...选项中的数据,本来是存在this._data、this._props上,但是我们在render中却可以直接通过this.xxx来访问data、props...选项中的数据,这就是因为Vue在初始化时帮我们做了代理。同样的在Vue3也一样,因此这里对instance.ctx做了代理)。
  3. 如果有setup hook就调用。

接着咱们分析下PublicInstanceProxyHandlers,来看看属性访问缓存对象的作用。 源码在packages/runtime-core/src/componentPublicInstance.ts中:

Vue3源码阅读——组件创建及其初始化过程

上面图中是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,由于事先并不知道这个xxxkey到底存在哪个数据对象中,因此不得不从setupState、data、ctx、props中按照先后顺序调用hasOwn判断key是否存在。如果每次访问都来这么判断,显然开销是巨大的。因此就有了accessCache,基于 空间换时间 的思想,当第一次找到了某个key存在哪个数据对象,就把这个key存在accessCache中,值就是包含这个key的数据对象,下一次再来访问这个key,就不需要再去判断hasOwn,直接从对应的数据对象中取值返回即可。

源码中也有注释说明了这一点:

Vue3源码阅读——组件创建及其初始化过程

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

Vue3源码阅读——组件创建及其初始化过程

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

Vue3源码阅读——组件创建及其初始化过程

来看下set的逻辑:

Vue3源码阅读——组件创建及其初始化过程

has的逻辑:

Vue3源码阅读——组件创建及其初始化过程

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

Vue3源码阅读——组件创建及其初始化过程

逻辑如下:

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

Vue3源码阅读——组件创建及其初始化过程

  1. 调用setCurrentInstance,设置当前实例。
  2. 调用pauseTracking,设置暂停收集依赖。
  3. 通过callWithErrorHandling调用setup hook,毕竟setup hook是用户写的,只要是用户写的代码在框架内调用,都需要捕获未知异常,不能因为用户写的异常导致了框架的运行出错。

    Vue3源码阅读——组件创建及其初始化过程

  4. 调用resetTracking,重置shouldTrack
  5. 调用unsetCurrentinstance,重置当前实例。
  6. 处理setup hook返回值是promise的情况。
  7. 如果setup hook返回值不是promise,就调用handleSetupResult

接下来咱们看下handleSetupResult

Vue3源码阅读——组件创建及其初始化过程

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

Vue3源码阅读——组件创建及其初始化过程

Vue3源码阅读——组件创建及其初始化过程

finishComponentSetup的源码能够看出,finishComponentSetup主要的逻辑就是在标准化rendertempldate、兼容OptionsAPI

接下来看下packages/runtime-core/src/componentOptions.tsapplyOptions的,由于源码较长,此处不截图了,感兴趣的可以到源码中详细阅读。此处大致列出兼容OptionsAPI做了哪些事情:

  1. 合并,解析选项。
  2. 如果有beforeCreate钩子,调用。
  3. 检查data、methods、computed、watch、inject中的重复key
  4. 处理data
  5. 处理methods
  6. 处理props
  7. 处理inject
  8. 处理computed
  9. 处理watch
  10. 处理provide
  11. 如果有created钩子,调用。
  12. 注册生命周期钩子。
  13. 处理expose
  14. 处理components
  15. 处理directives
  16. 处理filters

到此组件创建及其初始化过程的结束了。

总结

本文主要内容大致如下:

  1. 组件实例的创建逻辑分析
  2. 设置组件实例

其中设置组件实例又详细描述了:

  • 初始化slots、props,这两部分的逻辑比较容易理解,可自行阅读
  • 创建渲染上下文代理
  • 上下文对象代理key的缓存对象accessCache的作用及原理
  • 渲染过程中访问状态数据的过程
  • setup hook的调用逻辑以及setup hook返回值的处理
  • 标准化render、template
  • 兼容OptionsAPI

这是笔者第二篇源码分析类的文章,如果掘友们有什么建议,或者文中有错误,还请评论指出,谢谢!

如果本文对你有一点点帮助,点个赞支持一下吧,你的每一个【】都是我创作的最大动力 ^_^

转载自:https://juejin.cn/post/7244018340879155258
评论
请登录