Vue3源码学习 -- 4、Composition API

概述

Composition API 即组合式API,主要包括 setup、生命周期钩子、getCurrentInstance、Provide/Inject
产生这组API的动机是提高代码的可维护性、可复用性以及消除this。
下面 setup 中的代码和注释中的代码实现的效果是一样的。

<script>
  const app = Vue.createApp({
    // data(){
    //   return {
    //     counter:1,
    //   }
    // },
    // mounted(){
    //   setInterval(() => { this.counter++ }, 1000)
    // },
    setup(){
      const counter = Vue.ref(0);
      Vue.onMounted(()=>{
        setInterval(() => { counter.value++ }, 1000)
      })
      return { counter }
    }
  })

  app.mount('#app');
</script>

setup执行顺序

在探索初始化流程时,我们了解到 Vue3 会在 mountComponent 方法中创建当前组件实例,并通过 setupComponent 方法对当前组件进行初始化操作。
在这里插入图片描述
在 setupComponent 中,首先对组件的 props 和 slots 选项进行了初始化,然后执行了 setupStatefulComponent 方法,该方法的返回值就是我们在 setup 选项中写的 return 在经过处理后的值。
在这里插入图片描述
在 setupStatefulComponent 中,先对 instance.ctx 做了代理,让其变成响应式的。然后通过 createSetupContext 方法创建了 setup 的上下文对象,在该方法中返回了对外暴露接口的 expose 、只读的组件非属性特性 attrs 以及 emit 和 slots 。接下来通过setCurrentInstance 方法设置组件实例,以便我们在 setup 中通过 getCurrentInstance 获取组件实例。然后通过 callWithErrorHandling 方法执行 setup 得到 setupResult 。
在这里插入图片描述
在这里插入图片描述
接下来通过 handleSetupResult 处理返回结果。如果 setupResult 是函数,则作为 render 函数处理,也就是说 setup 支持通过渲染函数来渲染组件。当然,正常情况下我们在 setup 中 return 的都是一个对象,如果是对象,就通过代理将其转换为响应式对象。最后执行 finishComponentSetup 。
在这里插入图片描述
finishComponentSetup 方法中最重要的事情就是支持 Vue2,通过 applyOptions 来处理 Vue2 中的 options API 。
在这里插入图片描述
通过上面的流程,我们会发现,在执行 setup 时,组件实例 instance 已经创建了,那么 beforeCreate 和 created 应该在 setup 之前执行。但是在这里 setup 会先处理,最后才通过 applyOptions 处理 Vue2 中的 options API 。所以,如果我们在示例代码中同时写上 setup 和 created ,那么应该是 setup 先执行。

在这里插入图片描述

在这里插入图片描述

setup 和 data 的关系

通过上面的探究,我们知道了 Vue3 对 Vue2 做了兼容处理,那么如果 setup 和 data 同时存在,Vue3会如何处理呢?如果 setup 和 data 中存在同名的属性,谁的优先级更高呢?我们可以在示例中做个实验。在这里插入图片描述
可以看到,在 setup 和 data 中存在相同属性时,setup 的优先级更高,并且 data 中的属性也是可以生效的。
在这里插入图片描述
我们再进入源码中对组件实例做代理的地方看一下 Vue3 是怎么处理的。
在这里插入图片描述
在 setupStatefulComponent 中,通过 PublicInstanceProxyHandlers 对组件实例的属性进行了拦截,并在其 get 方法中做了逻辑处理。
在这里插入图片描述
由图中的取值顺序我们知道,在有缓存的时候,会先从 setup 中取值,然后是 data ,再是组件上下文,最后是 props 。
在这里插入图片描述
在没有缓存时,依然是先从 setup 中取值,再从 data 中取值的。

setup 中的生命周期钩子

我们知道,在 Vue2 中,每个组件的生命周期的钩子只能写一次,但是在 Vue3 的 setup 中,相同的钩子可以写多次,都是可以生效的。
在这里插入图片描述

在这里插入图片描述
那么 Vue3 是怎么实现的呢?
我们打开 packages\runtime-core\src\apiLifecycle.ts 文件,可以看到所有的生命周期钩子都是通过 createHook 这个工厂函数创建的。在 createHook 返回的函数中,通过 injectHook 注入钩子。
在这里插入图片描述
在 injectHook 中,传入了4个参数,type 是生命周期钩子名称,hook 是用户传入的要进行处理的函数,target 是组件实例,prepend 是控制函数插入执行数组位置的标识。
首先从组件实例中取出对应生命周期钩子的执行数组,如果不存在则创建一个空数组。然后将 hook 包装为 wrappedHook ,这个包装的主要作用是做错误处理,当函数执行出错时可以捕获错误原因。再将包装后的 wrappedHook 放入执行数组中,等待执行,最后将 wrappedHook 返回。

在这里插入图片描述
至于生命周期钩子的执行,是在组件初始化和更新流程中对应的时刻进行的。在 packages\runtime-core\src\renderer.ts 文件的 setupRenderEffect 中,我们可以看到,在 componentUpdateFn 中,会从组件实例中取出钩子函数数组并在相应的时刻执行。
在这里插入图片描述

provide/inject

我们在使用 provide/inject 时,在 setup 中通过 provide 提供数据,在后代组件中通过 inject 获取数据。
在这里插入图片描述

在这里插入图片描述
接下来我们就进入源码看一下这组 api 是如何实现的。
我们在 packages\runtime-core\src\apiInject.ts 文件中可以看到这两个方法的声明。
在 provide 中,先取出当前组件的 provides 和其父组件(如果有)的 provides ,当父组件的 provides 和当前组件的 provides 相等时,通过 Object.create 将当前组件的 provides 的原型对象设为父组件的 provides ,当子孙组件获取数据时,就可以通过原型链完成查找。
在这里插入图片描述
那么什么情况下当前组件的 provides 会和 父组件的 provides 相等呢?
其实在组件初始化的过程中通过 createComponentInstance 创建组件实例时就将父组件的 provides 赋值给了当前组件。也就是说,除了最初的根组件,其他组件的 provides 和父组件的 provides 都是相等的。因为根组件的 parentProvides 为 null,而在组件初始化时会将根组件的 provides 赋值为一个空对象。
在这里插入图片描述
再看一下 inject 的实现。
inject 接收3个参数,第一个是属性名,第二个是默认值,这个默认值可能为函数,第三个是表示默认值是否为函数的标识。
我们看到在 inject 中,如果当前组件是根组件,则从组件实例中取 provides ,否则从父组件中取 provides ,然后将 provides 中对应的属性值返回。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值