Vue3全局组件通信之provide/inject源码分析
1、前言
顾名思义,爷孙组件是比 父子组件通信 要更深层次的引用关系(也有称之为 “隔代组件”):
C组件引入到B组件里,B组件引入到A组件里渲染,此时A是C的爷爷级别(可能还有更多层级关系),如果你用 props ,只能一级一级传递下去,那就太繁琐了,因此我们需要更直接的通信方式。
他们之间的关系如下,Grandson.vue 并非直接挂载在 Grandfather.vue 下面,他们之间还隔着至少一个 Son.vue (可能有多个):
Grandfather.vue └─Son.vue └─Grandson.vue
因为上下级的关系的一致性,爷孙组件通信的方案也适用于 父子组件通信 ,只需要把爷孙关系换成父子关系即可。
2、provide / inject
这个特性有两个部分:Grandfather.vue 有一个 provide 选项来提供数据,Grandson.vue 有一个 inject 选项来开始使用这些数据。
Grandfather.vue 通过 provide 向 Grandson.vue 传值(可包含定义好的函数)
Grandson.vue 通过 inject 向 Grandfather.vue 触发爷爷组件的事件执行
无论组件层次结构有多深,发起 provide 的组件都可以作为其所有下级组件的依赖提供者
这一部分的内容变化都特别大,但使用起来其实也很简单,不用慌,也有相同的地方:
父组件不需要知道哪些子组件使用它 provide 的 property
子组件不需要知道 inject property 来自哪里
另外,要切记一点就是:provide 和 inject 绑定并不是可响应的。这是刻意为之的,但如果传入了一个可监听的对象,那么其对象的 property 还是可响应的。
3、发起 provide
我们先来回顾一下 2.x 的用法:
export default { // 定义好数据 data () { return { tags: [ '中餐', '粤菜', '烧腊' ] } }, // provide出去 provide () { return { tags: this.tags } } }
旧版的 provide 用法和 data 类似,都是配置为一个返回对象的函数。
3.x 的新版 provide, 和 2.x 的用法区别比较大。
在 3.x , provide 需要导入并在 setup 里启用,并且现在是一个全新的方法。
每次要 provide 一个数据的时候,就要单独调用一次。
每次调用的时候,都需要传入 2 个参数:
参数 | 类型 | 说明 |
---|---|---|
key | string | 数据的名称 |
value | any | 数据的值 |
来看一下如何创建一个 provide:
// 记得导入provide import { defineComponent, provide } from 'vue' export default defineComponent({ // ... setup () { // 定义好数据 const msg: string = 'Hello World!'; // provide出去 provide('msg', msg); } })
操作非常简单对吧,但需要注意的是,provide 不是响应式的,如果你要使其具备响应性,你需要传入响应式数据
4、接收 inject
也是先来回顾一下 2.x 的用法:
export default { inject: ['tags'], mounted () { console.log(this.tags); } }
旧版的 inject 用法和 props 类似,3.x 的新版 inject, 和 2.x 的用法区别也是比较大。
在 3.x, inject 和 provide 一样,也是需要先导入然后在 setup 里启用,也是一个全新的方法。
每次要 inject 一个数据的时候,就要单独调用一次。
每次调用的时候,只需要传入 1 个参数:
参数 | 类型 | 说明 |
---|---|---|
key | string | 与provide对应的数据名称 |
// 记得导入inject import { defineComponent, inject } from 'vue' export default defineComponent({ // ... setup () { const msg: string = inject('msg') || ''; } })
也是很简单(写 TS 的话,由于 inject 到的值可能是 undefined,所以要么加个 undefined 类型,要么给变量设置一个空的默认值)。
5、响应性数据的传递与接收
之所以要单独拿出来说, 是因为变化真的很大
在前面我们已经知道,provide 和 inject 本身不可响应,但是并非完全不能够拿到响应的结果,只需要我们传入的数据具备响应性,它依然能够提供响应支持。
我们以 ref 和 reactive 为例,来看看应该怎么发起 provide 和接收 inject。
先在 Grandfather.vue 里 provide 数据:
export default defineComponent({ // ... setup () { // provide一个ref const msg = ref<string>('Hello World!'); provide('msg', msg); // provide一个reactive const userInfo: Member = reactive({ id: 1, name: 'Petter' }); provide('userInfo', userInfo); // 2s 后更新数据 setTimeout(() => { // 修改消息内容 msg.value = 'Hi World!'; // 修改用户名 userInfo.name = 'Tom'; }, 2000); } })
在 Grandsun.vue 里 inject 拿到数据:
export default defineComponent({ setup () { // 获取数据 const msg = inject('msg'); const userInfo = inject('userInfo'); // 打印刚刚拿到的数据 console.log(msg); console.log(userInfo); // 因为 2s 后数据会变,我们 3s 后再看下,可以争取拿到新的数据 setTimeout(() => { console.log(msg); console.log(userInfo); }, 3000); // 响应式数据还可以直接给 template 使用,会实时更新 return { msg, userInfo } } })
非常简单,非常方便!!!
响应式的数据 provide 出去,在子孙组件拿到的也是响应式的,并且可以如同自身定义的响应式变量一样,直接 return 给 template 使用,一旦数据有变化,视图也会立即更新。
但上面这句话有效的前提是,不破坏数据的响应性,比如 ref 变量,你需要完整的传入,而不能只传入它的 value,对于 reactive 也是同理,不能直接解构去破坏原本的响应性。
切记!切记!!!
6、引用类型的传递与接收 (针对非响应性数据的处理)
provide 和 inject 并不是可响应的,这是官方的故意设计,但是由于引用类型的特殊性,在子孙组件拿到了数据之后,他们的属性还是可以正常的响应变化。
先在 Grandfather.vue 里 provide 数据:
export default defineComponent({ // ... setup () { // provide 一个数组 const tags: string[] = [ '中餐', '粤菜', '烧腊' ]; provide('tags', tags); // provide 一个对象 const userInfo: Member = { id: 1, name: 'Petter' }; provide('userInfo', userInfo); // 2s 后更新数据 setTimeout(() => { // 增加tags的长度 tags.push('叉烧'); // 修改userInfo的属性值 userInfo.name = 'Tom'; }, 2000); } })
在 Grandsun.vue 里 inject 拿到数据:
export default defineComponent({ setup () { // 获取数据 const tags: string[] = inject('tags') || []; const userInfo: Member = inject('userInfo') || { id: 0, name: '' }; // 打印刚刚拿到的数据 console.log(tags); console.log(tags.length); console.log(userInfo); // 因为 2s 后数据会变,我们 3s 后再看下,能够看到已经是更新后的数据了 setTimeout(() => { console.log(tags); console.log(tags.length); console.log(userInfo); }, 3000); } }) export default defineComponent({ setup () { // 获取数据 const tags: string[] = inject('tags') || []; const userInfo: Member = inject('userInfo') || { id: 0, name: '' }; // 打印刚刚拿到的数据 console.log(tags); console.log(tags.length); console.log(userInfo); // 因为 2s 后数据会变,我们 3s 后再看下,能够看到已经是更新后的数据了 setTimeout(() => { console.log(tags); console.log(tags.length); console.log(userInfo); }, 3000); } })
引用类型的数据,拿到后可以直接用,属性的值更新后,子孙组件也会被更新。
但是!!!由于不具备真正的响应性,return 给模板使用依然不会更新视图,如果涉及到视图的数据,请依然使用 响应式 API 。
7、基本类型的传递与接收 (针对非响应性数据的处理)
基本数据类型被直接 provide 出去后,再怎么修改,都无法更新下去,子孙组件拿到的永远是第一次的那个值。
先在 Grandfather.vue 里 provide 数据:
export default defineComponent({ // ... setup () { // provide 一个数组的长度 const tags: string[] = [ '中餐', '粤菜', '烧腊' ]; provide('tagsCount', tags.length); // provide 一个字符串 let name: string = 'Petter'; provide('name', name); // 2s 后更新数据 setTimeout(() => { // tagsCount 在 Grandson 那边依然是 3 tags.push('叉烧'); // name 在 Grandson 那边依然是 Petter name = 'Tom'; }, 2000); } })
在 Grandsun.vue 里 inject 拿到数据:
export default defineComponent({ setup () { // 获取数据 const name: string = inject('name') || ''; const tagsCount: number = inject('tagsCount') || 0; // 打印刚刚拿到的数据 console.log(name); console.log(tagsCount); // 因为 2s 后数据会变,我们 3s 后再看下 setTimeout(() => { // 依然是 Petter console.log(name); // 依然是 3 console.log(tagsCount); }, 3000); } })
很失望,并没有变化。
那么是否一定要定义成响应式数据或者引用类型数据呢?
当然不是,我们在 provide 的时候,也可以稍作修改,让它能够同步更新下去。
先在 Grandfather.vue 里 provide 数据:
export default defineComponent({ // ... setup () { // provide 一个数组的长度 const tags: string[] = [ '中餐', '粤菜', '烧腊' ]; provide('tagsCount', (): number => { return tags.length; }); // provide 字符串 let name: string = 'Petter'; provide('name', (): string => { return name; }); // 2s 后更新数据 setTimeout(() => { // tagsCount 现在可以正常拿到 4 了 tags.push('叉烧'); // name 现在可以正常拿到 Tom 了 name = 'Tom'; }, 2000); } })
再来 Grandsun.vue 里修改一下 inject 的方式,看看这次拿到的数据:
export default defineComponent({ setup () { // 获取数据 const tagsCount: any = inject('tagsCount'); const name: any = inject('name'); // 打印刚刚拿到的数据 console.log(tagsCount()); console.log(name()); // 因为 2s 后数据会变,我们 3s 后再看下 setTimeout(() => { // 现在可以正确得到 4 console.log(tagsCount()); // 现在可以正确得到 Tom console.log(name()); }, 3000); } })
这次可以正确拿到数据了,看出这2次的写法有什么区别了吗?
基本数据类型,需要 provide 一个函数,将其 return 出去给子孙组件用,这样子孙组件每次拿到的数据才会是新的。
但由于不具备响应性,所以子孙组件每次都需要重新通过执行 inject 得到的函数才能拿到最新的数据。
按我个人习惯来说,使用起来挺别扭的,能不用就不用……
由于不具备真正的响应性,return 给模板使用依然不会更新视图,如果涉及到视图的数据,请依然使用 响应式 API 。
以上是Vue3全局组件通信之provide/inject源码分析的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

vue3+vite:src使用require动态导入图片报错和解决方法vue3+vite动态的导入多张图片vue3如果使用的是typescript开发,就会出现require引入图片报错,requireisnotdefined不能像使用vue2这样imgUrl:require(’…/assets/test.png’)导入,是因为typescript不支持require所以用import导入,下面介绍如何解决:使用awaitimport

tinymce是一个功能齐全的富文本编辑器插件,但在vue中引入tinymce并不像别的Vue富文本插件一样那么顺利,tinymce本身并不适配Vue,还需要引入@tinymce/tinymce-vue,并且它是国外的富文本插件,没有通过中文版本,需要在其官网下载翻译包(可能需要翻墙)。1、安装相关依赖npminstalltinymce-Snpminstall@tinymce/tinymce-vue-S2、下载中文包3.引入皮肤和汉化包在项目public文件夹下新建tinymce文件夹,将下载的

Vue实现博客前端,需要实现markdown的解析,如果有代码则需要实现代码的高亮。Vue的markdown解析库有很多,如markdown-it、vue-markdown-loader、marked、vue-markdown等。这些库都大同小异。这里选用的是marked,代码高亮的库选用的是highlight.js。具体实现步骤如下:一、安装依赖库在vue项目下打开命令窗口,并输入以下命令npminstallmarked-save//marked用于将markdown转换成htmlnpmins

想要实现页面的局部刷新,我们只需要实现局部组件(dom)的重新渲染。在Vue中,想要实现这一效果最简便的方式方法就是使用v-if指令。在Vue2中我们除了使用v-if指令让局部dom的重新渲染,也可以新建一个空白组件,需要刷新局部页面时跳转至这个空白组件页面,然后在空白组件内的beforeRouteEnter守卫中又跳转回原来的页面。如下图所示,如何在Vue3.X中实现点击刷新按钮实现红框范围内的dom重新加载,并展示对应的加载状态。由于Vue3.X中scriptsetup语法中组件内守卫只有o

前言无论是vue还是react,当遇到多处重复代码的时候,我们都会想着如何复用这些代码,而不是一个文件里充斥着一堆冗余代码。实际上,vue和react都可以通过抽组件的方式来达到复用,但如果遇到一些很小的代码片段,你又不想抽到另外一个文件的情况下,相比而言,react可以在相同文件里面声明对应的小组件,或者通过renderfunction来实现,如:constDemo:FC=({msg})=>{returndemomsgis{msg}}constApp:FC=()=>{return(

最终效果安装VueCropper组件yarnaddvue-cropper@next上面的安装值针对Vue3的,如果时Vue2或者想使用其他的方式引用,请访问它的npm官方地址:官方教程。在组件中引用使用时也很简单,只需要引入对应的组件和它的样式文件,我这里没有在全局引用,只在我的组件文件中引入import{userInfoByRequest}from'../js/api'import{VueCropper}from'vue-cropper&

使用Vue构建自定义元素WebComponents是一组web原生API的统称,允许开发者创建可复用的自定义元素(customelements)。自定义元素的主要好处是,它们可以在使用任何框架,甚至是在不使用框架的场景下使用。当你面向的最终用户可能使用了不同的前端技术栈,或是当你希望将最终的应用与它使用的组件实现细节解耦时,它们会是理想的选择。Vue和WebComponents是互补的技术,Vue为使用和创建自定义元素提供了出色的支持。你可以将自定义元素集成到现有的Vue应用中,或使用Vue来构

vue3+ts+axios+pinia实现无感刷新1.先在项目中下载aiXos和pinianpmipinia--savenpminstallaxios--save2.封装axios请求-----下载js-cookienpmiJS-cookie-s//引入aixosimporttype{AxiosRequestConfig,AxiosResponse}from"axios";importaxiosfrom'axios';import{ElMess
