你不知道的 vue3:使用 runWithContext 实现在非 setup 期间使用 inject
前言
日常开发时有些特殊的场景需要在非 setup
期间调用inject
函数,比如 app 中使用provide
注入的配置信息需要在发送http
请求时带上传给后端。对此我们希望不在每个发起请求的地方去修改,而是在发起请求前的拦截进行统一处理,对此我们就需要在拦截请求的函数中使用inject
拿到app
注入的配置信息。
为什么只能在setup
期间调用inject
函数
inject
的用法大家应该都清楚,是一个用于注入依赖的函数,它可以将父组件或根组件 app 中通过 provide 提供的相同 key 的值注入到当前组件中。
我们先来看看简化后的provider
和inject
的源码,其实非常简单。
provider
函数源码
我们先来看看简化后的provider
函数源码,其实很简单:
在初始化一个vue
实例的时候会将父组件的provides
对象赋值给当前实例的provides
对象,所以当第一次provide
方法被调用后,会判断当前的provides
对象是否等于父组件provides
对象,如果相等就会基于父组件实例的provides
对象拷贝一个新的provides
对象。
此时父组件和子组件的provides
对象经过Object.create(parentProvides)
后就已经不是同一个对象了。如果子组件和父组件provide
对象中都有相同的key
,经过provides[key] = value
后就会将原本父组件赋值的相同key
的值“覆盖”掉。因为父组件的provides
对象是从他的父组件provides
对象拷贝的而来,所以子组件包含了父组件链上的所有的provide
提供的值。
机智如你现在应该能够理解为什么官网会说“父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值”。
inject
函数源码
现在我们再来看看简化后的inject
函数源码,同样也非常简单:
我们首先来看看currentInstance
这个全局变量,setup
只会在初始化 vue 实例的时候执行一次,在setup
期间currentInstance
会被赋值为当前组件的 vue 实例。等 vue 实例初始化完成后currentInstance
就会被赋值为null
。
前面我们已经介绍了组件的provides
对象中是包含了父组件链上的所有provides
的 key,所以我们这里只需要从当前vue
实例instance
的parent
中的provides
对象中就可以取出注入相同key
的值。
看到这里相信你已经知道了为什么只能在setup
期间调用调用inject
方法了。因为只有在setup
期间currentInstance
全局变量的值为当前组件的vue
实例对象,当vue
实例初始化完成后currentInstance
已经被赋值为 null。所以当我们在非setup
期间调用inject
方法会警告:inject() can only be used inside setup() or functional components.
至于currentApp
其实是另外一个全局变量,在调用app.runWithContext
方法时会给它赋值,这个下一节我们讲app.runWithContext
的时候会详细讲。
使用app.runWithContext()
打破inject
只能在setup
期间调用的限制
app.runWithContext()
的官方解释为“使用当前应用作为注入上下文执行回调函数”。这个解释乍一看很容易一脸懵逼,不着急我慢慢给你解释。
我们先来看看runWithContext
方法接收的参数和返回的值。这个方法接收一个参数,参数是一个回调函数。这个回调函数会在app.runWithContext()
执行时被立即执行,并且app.runWithContext()
的返回值就是回调函数的返回值。
我们再来看一个使用runWithContext
的例子,这行代码是拦截请求时才执行。作用是拿到app
中注入的userType
字段,注意不是在setup
期间执行。
按照我们前一节的分析,inject
需要在setup
中执行才能拿到当前的vue
实例。但是之前还有一个currentApp
变量我们没有解释,再来回顾一下上一节的inject
源码。如果我们拿不到当前的vue
实例,就会去看一下全局变量 currentApp 是否存在,如果存在那么就从currentApp
中去拿provides
对象。这个currentApp
就是官方解释的“注入的上下文”,所以我们才可以在非setup
期间执行inject
,并且还可以拿到注入的值。
我们再来看看runWithContext
的源码,其实非常简单。
这里的app
就是调用runWithContext
方法的对象,你可以简单的理解为this
。调用app.runWithContext()
就会将app
对象赋值给全局变量currentApp
,然后会立即执行传入的回调fn
。当执行到回调中的inject("userType")
时,由于我们在上一行代码已经给全局变量currentApp
赋值为app
了,所以就可以从 app 中拿到对应 key 的provider
值。
总结
这篇文章我们先介绍了由于inject
执行期间需要拿到当前的 vue 实例,然后才能从父组件提供的provides
对象中找到相同 key 的值。如果我们在非 setup
期间执行,那么就拿不到当前 vue 实例。也找不到父组件,当然inject
也没法拿到注入的值。
在一些场景中我们确实需要在非 setup
期间执行inject
,这时我们就可以使用app.runWithContext()
将app
对象作为注入上下文执行回调函数。然后在inject
执行期间就能从app
中拿到提供的provides
对象中相同key
的值。
文章转载自:欧阳码农
评论