写点什么

你不知道的 vue3:使用 runWithContext 实现在非 setup 期间使用 inject

  • 2024-01-18
    福建
  • 本文字数:2833 字

    阅读完需:约 9 分钟

前言


日常开发时有些特殊的场景需要在非 setup 期间调用inject函数,比如 app 中使用provide注入的配置信息需要在发送http请求时带上传给后端。对此我们希望不在每个发起请求的地方去修改,而是在发起请求前的拦截进行统一处理,对此我们就需要在拦截请求的函数中使用inject拿到app注入的配置信息。


为什么只能在setup 期间调用inject函数


inject的用法大家应该都清楚,是一个用于注入依赖的函数,它可以将父组件或根组件 app 中通过 provide 提供的相同 key 的值注入到当前组件中。


我们先来看看简化后的providerinject的源码,其实非常简单。


provider函数源码


我们先来看看简化后的provider函数源码,其实很简单:


export function provide(  key,  value,) {  //拿到当前组件的vue实例提供的provides对象  let provides = currentInstance.provides  //拿到父组件的vue实例提供的provides对象  const parentProvides =    currentInstance.parent && currentInstance.parent.provides  // 如果父组件和当前组件的provides对象相等  if (parentProvides === provides) {    // 基于父组件的provides对象拷贝出一个新的对象    provides = currentInstance.provides = Object.create(parentProvides)  }  // 如果provides对象中有相同的key,那么就会直接覆盖。  provides[key] = value}
复制代码


在初始化一个vue实例的时候会将父组件的provides对象赋值给当前实例的provides对象,所以当第一次provide方法被调用后,会判断当前的provides对象是否等于父组件provides对象,如果相等就会基于父组件实例的provides对象拷贝一个新的provides对象。


此时父组件和子组件的provides对象经过Object.create(parentProvides)后就已经不是同一个对象了。如果子组件和父组件provide对象中都有相同的key,经过provides[key] = value后就会将原本父组件赋值的相同key的值“覆盖”掉。因为父组件的provides对象是从他的父组件provides对象拷贝的而来,所以子组件包含了父组件链上的所有的provide提供的值。


机智如你现在应该能够理解为什么官网会说“父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值”。


inject函数源码


现在我们再来看看简化后的inject函数源码,同样也非常简单:


export function inject(  key,) {  //currentInstance是一个存储当前vue实例的全局变量,在vue组件初始化时会赋值。  //初始化完成后会被重置为null  const instance = currentInstance
if (instance || currentApp) { // 拿到父组件或者currentApp中提供的provides对象 const provides = instance ? instance.parent.provides : currentApp!._context.provides // 从provides对象中拿到相同key的值 if (provides && key in provides) { return provides[key] } } else if (__DEV__) { // 不是在setup中或者runWithContext中调用,就会发出警告 warn(`inject() can only be used inside setup() or functional components.`) }}
复制代码


我们首先来看看currentInstance这个全局变量,setup只会在初始化 vue 实例的时候执行一次,在setup期间currentInstance会被赋值为当前组件的 vue 实例。等 vue 实例初始化完成后currentInstance就会被赋值为null


前面我们已经介绍了组件的provides对象中是包含了父组件链上的所有provides的 key,所以我们这里只需要从当前vue实例instanceparent中的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期间执行。


const userType = app.runWithContext(() => {  // 拿到app中注入的userType字段  return inject("userType");});
复制代码


按照我们前一节的分析,inject需要在setup中执行才能拿到当前的vue实例。但是之前还有一个currentApp变量我们没有解释,再来回顾一下上一节的inject源码。如果我们拿不到当前的vue实例,就会去看一下全局变量 currentApp 是否存在,如果存在那么就从currentApp中去拿provides对象。这个currentApp就是官方解释的“注入的上下文”,所以我们才可以在非setup期间执行inject,并且还可以拿到注入的值。


if (instance || currentApp) {  // 拿到父组件或者currentApp中提供的provides对象  const provides = instance    ? instance.parent.provides    : currentApp!._context.provides  // 从provides对象中拿到相同key的值  if (provides && key in provides) {    return provides[key]  }}
复制代码


我们再来看看runWithContext的源码,其实非常简单。


runWithContext(fn) {  // 将调用runWithContext方法的对象赋值给全局对象currentApp  currentApp = app  try {    // 立即执行传入的回调函数    return fn()  } finally {    currentApp = null  }}
复制代码


这里的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的值。


文章转载自:欧阳码农

原文链接:https://www.cnblogs.com/heavenYJJ/p/17970284

体验地址:http://www.jnpfsoft.com/?from=001

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
你不知道的vue3:使用runWithContext实现在非 setup 期间使用inject_Java_快乐非自愿限量之名_InfoQ写作社区