你不知道的vue3:使用runWithContext实现在非 setup 期间使用inject
前言
日常开发时有些特殊的场景需要在非 setup 期间调用inject函数,比如app中使用provide注入的配置信息需要在发送http请求时带上传给后端。对此我们希望不在每个发起请求的地方去修改,而是在发起请求前的拦截进行统一处理,对此我们就需要在拦截请求的函数中使用inject拿到app注入的配置信息。
为什么只能在setup 期间调用inject函数
inject的用法大家应该都清楚,是一个用于注入依赖的函数,它可以将父组件或根组件 app 中通过 provide 提供的相同 key 的值注入到当前组件中。
我们先来看看简化后的provider和inject的源码,其实非常简单。
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实例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期间执行。
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的值。
如果我的文章对你有点帮助,欢迎关注公众号:【欧阳码农】,文章在公众号首发。你的支持就是我创作的最大动力,感谢感谢!
你不知道的vue3:使用runWithContext实现在非 setup 期间使用inject的更多相关文章
- vue3 学习笔记 (四)——vue3 setup() 高级用法
本篇文章干货较多,建议收藏! 从 vue2 升级到 vue3,vue3 是可以兼容 vue2 的,所以 vue3 可以采用 vue2 的选项式API.由于选项式API一个变量存在于多处,如果出现问题时 ...
- VUE3.x 前瞻
文档: API Reference 教程 课件 1. 初始化项目 // ① npm i -g @vue/cli // ② vue create my-project // ③ npm install ...
- Vue3.0新特性
Vue3.0新特性 Vue3.0的设计目标可以概括为体积更小.速度更快.加强TypeScript支持.加强API设计一致性.提高自身可维护性.开放更多底层功能. 描述 从Vue2到Vue3在一些比较重 ...
- Vue3笔记(二)了解组合式API的应用与方法
一.组合式API(Composition API)的介绍 官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html 组 ...
- vue3 vite等笔记
1.脚手架 vue-cli基于webpack封装,生态非常强大,可配置性也非常高,几乎能够满足前端工程化的所有要求.缺点就是配置复杂,甚至有公司有专门的webpack工程师专门做配置,另外就是webp ...
- vue3组合式API介绍
为什么要使用Composition API? 根据官方的说法,vue3.0的变化包括性能上的改进.更小的 bundle 体积.对 TypeScript 更好的支持.用于处理大规模用例的全新 API,全 ...
- 尝鲜 vue3.x 新特性 - CompositionAPI
0. 基础要求 了解常见的 ES6 新特性 ES6 的导入导出语法 解构赋值 箭头函数 etc... 了解 vue 2.x 的基本使用 组件 常用的指令 生命周期函数 computed.watch.r ...
- 通过10个实例小练习,快速熟练 Vue3.0 核心新特性
Vue3.0 发 beta 版都有一段时间了,正式版也不远了,所以真的要学习一下 Vue3.0 的语法了. GitHub 博客地址: https://github.com/biaochenxuying ...
- vue3系列:vue3.0自定义弹框组件V3Popup|vue3.x手机端弹框组件
基于Vue3.0开发的轻量级手机端弹框组件V3Popup. 之前有分享一个vue2.x移动端弹框组件,今天给大家带来的是Vue3实现自定义弹框组件. V3Popup 基于vue3.x实现的移动端弹出框 ...
- vue3系列:vue3.0自定义全局弹层V3Layer|vue3.x pc桌面端弹窗组件
基于Vue3.0开发PC桌面端自定义对话框组件V3Layer. 前两天有分享一个vue3.0移动端弹出层组件,今天分享的是最新开发的vue3.0版pc端弹窗组件. V3Layer 一款使用vue3.0 ...
随机推荐
- Welcome to YARP - 8.分布式跟踪
Welcome to YARP - 1.认识YARP并搭建反向代理服务 Welcome to YARP - 2.配置功能 2.1 - 配置文件(Configuration Files) 2.2 - 配 ...
- 实例讲解SpringBoot集成Dubbo的步骤及过程
首先,让我们先了解一下Spring Boot和Dubbo. Spring Boot 是一个开源的 Java Web 框架,它可以帮助开发者快速创建独立的.生产级别的 Spring 应用程序.Sprin ...
- C/C++ 通过SQLiteSDK增删改查
SQLite,作为一款嵌入式关系型数据库管理系统,一直以其轻量级.零配置以及跨平台等特性而备受青睐.不同于传统的数据库系统,SQLite是一个库,直接与应用程序一同编译和链接,无需单独的数据库服务器进 ...
- 使用咱们公司的DataInside可视化产品配置了一个教育行业的大屏展示软件
今天在公司用配置了一个可视化大屏软件,大家觉得如何?
- 数据分析工具 Datainside、Power BI、帆软(FineReport)怎么选?
Datainside.Power BI和帆软(FineReport)都是数据分析领域常用的工具,它们各自有不同的特点和适用场景.下面将会详细介绍每个工具的功能和优势,以便您进行选择. Datainsi ...
- 0x05.HelloJAVA
基础知识 java的类目和文件名必须相同(区分大小写) java文件,先编译成字节码(.class文件),然后在JAVA的虚拟机JVM上以解释方式执行字节码 java的项目里面包含了源代码.依赖.配置 ...
- 浅谈SQL优化小技巧
回顾MySQL的执行过程,帮助介绍如何进行sql优化. (1)客户端发送一条查询语句到服务器: (2)服务器先查询缓存,如果命中缓存,则立即返回存储在缓存中的数据: (3)未命中缓存后,MySQL通过 ...
- 本地部署modelscope-agent
本地部署modelscope-agent 部署流程 在modelscope社区创建一个自己的空间(假设name是LocalAgent),clone空间到本地(或云服务器如魔搭Notebook) git ...
- 14、Map
1.Map的定义 map是Go中的内置类型,它将一个值与一个键关联起来.可以使用相应的键检索值.Map 是一种无序的键值对的集合.Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引 ...
- 从零玩转系列之微信支付实战PC端支付微信退款接口搭建 | 技术创作特训营第一期
一.前言 从零玩转系列之微信支付实战PC端支付微信退款接口搭建 | 技术创作特训营第一期 继前文章取消订单接口和查询订单接口此篇为申请退款流程,此篇文章过长我将分几个阶段的文章发布(项目源码都有,小程 ...