经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » Vue.js » 查看文章
Vue3全局组件通信之provide?/?inject详解
来源:jb51  时间:2023/3/15 8:52:42  对本文有异议

1、前言

顾名思义,爷孙组件是比 父子组件通信 要更深层次的引用关系(也有称之为 “隔代组件”):

C组件引入到B组件里,B组件引入到A组件里渲染,此时A是C的爷爷级别(可能还有更多层级关系),如果你用 props ,只能一级一级传递下去,那就太繁琐了,因此我们需要更直接的通信方式。

他们之间的关系如下,Grandson.vue 并非直接挂载在 Grandfather.vue 下面,他们之间还隔着至少一个 Son.vue (可能有多个):

  1. Grandfather.vue
  2. └─Son.vue
  3. └─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 的用法:

  1. export default {
  2. // 定义好数据
  3. data () {
  4. return {
  5. tags: [ '中餐', '粤菜', '烧腊' ]
  6. }
  7. },
  8. // provide出去
  9. provide () {
  10. return {
  11. tags: this.tags
  12. }
  13. }
  14. }

旧版的 provide 用法和 data 类似,都是配置为一个返回对象的函数。
3.x 的新版 provide, 和 2.x 的用法区别比较大。

??????
在 3.x , provide 需要导入并在 setup 里启用,并且现在是一个全新的方法。
每次要 provide 一个数据的时候,就要单独调用一次。
每次调用的时候,都需要传入 2 个参数:

参数类型说明
keystring数据的名称
valueany数据的值

来看一下如何创建一个 provide:

  1. // 记得导入provide
  2. import { defineComponent, provide } from 'vue'
  3.  
  4. export default defineComponent({
  5. // ...
  6. setup () {
  7. // 定义好数据
  8. const msg: string = 'Hello World!';
  9.  
  10. // provide出去
  11. provide('msg', msg);
  12. }
  13. })

操作非常简单对吧,但需要注意的是,provide 不是响应式的,如果你要使其具备响应性,你需要传入响应式数据

4、接收 inject

也是先来回顾一下 2.x 的用法:

  1. export default {
  2. inject: ['tags'],
  3. mounted () {
  4. console.log(this.tags);
  5. }
  6. }

旧版的 inject 用法和 props 类似,3.x 的新版 inject, 和 2.x 的用法区别也是比较大。

??????
在 3.x, inject 和 provide 一样,也是需要先导入然后在 setup 里启用,也是一个全新的方法。
每次要 inject 一个数据的时候,就要单独调用一次。
每次调用的时候,只需要传入 1 个参数:

参数类型说明
keystring与provide对应的数据名称
  1. // 记得导入inject
  2. import { defineComponent, inject } from 'vue'
  3.  
  4. export default defineComponent({
  5. // ...
  6. setup () {
  7. const msg: string = inject('msg') || '';
  8. }
  9. })

也是很简单(写 TS 的话,由于 inject 到的值可能是 undefined,所以要么加个 undefined 类型,要么给变量设置一个空的默认值)。

5、响应性数据的传递与接收

之所以要单独拿出来说, 是因为变化真的很大

在前面我们已经知道,provide 和 inject 本身不可响应,但是并非完全不能够拿到响应的结果,只需要我们传入的数据具备响应性,它依然能够提供响应支持。

我们以 ref 和 reactive 为例,来看看应该怎么发起 provide 和接收 inject。

先在 Grandfather.vue 里 provide 数据:

  1. export default defineComponent({
  2. // ...
  3. setup () {
  4. // provide一个ref
  5. const msg = ref<string>('Hello World!');
  6. provide('msg', msg);
  7.  
  8. // provide一个reactive
  9. const userInfo: Member = reactive({
  10. id: 1,
  11. name: 'Petter'
  12. });
  13. provide('userInfo', userInfo);
  14.  
  15. // 2s 后更新数据
  16. setTimeout(() => {
  17. // 修改消息内容
  18. msg.value = 'Hi World!';
  19.  
  20. // 修改用户名
  21. userInfo.name = 'Tom';
  22. }, 2000);
  23. }
  24. })

在 Grandsun.vue 里 inject 拿到数据:

  1. export default defineComponent({
  2. setup () {
  3. // 获取数据
  4. const msg = inject('msg');
  5. const userInfo = inject('userInfo');
  6.  
  7. // 打印刚刚拿到的数据
  8. console.log(msg);
  9. console.log(userInfo);
  10.  
  11. // 因为 2s 后数据会变,我们 3s 后再看下,可以争取拿到新的数据
  12. setTimeout(() => {
  13. console.log(msg);
  14. console.log(userInfo);
  15. }, 3000);
  16.  
  17. // 响应式数据还可以直接给 template 使用,会实时更新
  18. return {
  19. msg,
  20. userInfo
  21. }
  22. }
  23. })

非常简单,非常方便!!!

??????
响应式的数据 provide 出去,在子孙组件拿到的也是响应式的,并且可以如同自身定义的响应式变量一样,直接 return 给 template 使用,一旦数据有变化,视图也会立即更新。

但上面这句话有效的前提是,不破坏数据的响应性,比如 ref 变量,你需要完整的传入,而不能只传入它的 value,对于 reactive 也是同理,不能直接解构去破坏原本的响应性

切记!切记!!!

6、引用类型的传递与接收 (针对非响应性数据的处理)

provide 和 inject 并不是可响应的,这是官方的故意设计,但是由于引用类型的特殊性,在子孙组件拿到了数据之后,他们的属性还是可以正常的响应变化。

先在 Grandfather.vue 里 provide 数据:

  1. export default defineComponent({
  2. // ...
  3. setup () {
  4. // provide 一个数组
  5. const tags: string[] = [ '中餐', '粤菜', '烧腊' ];
  6. provide('tags', tags);
  7.  
  8. // provide 一个对象
  9. const userInfo: Member = {
  10. id: 1,
  11. name: 'Petter'
  12. };
  13. provide('userInfo', userInfo);
  14.  
  15. // 2s 后更新数据
  16. setTimeout(() => {
  17. // 增加tags的长度
  18. tags.push('叉烧');
  19.  
  20. // 修改userInfo的属性值
  21. userInfo.name = 'Tom';
  22. }, 2000);
  23. }
  24. })

在 Grandsun.vue 里 inject 拿到数据:

  1. export default defineComponent({
  2. setup () {
  3. // 获取数据
  4. const tags: string[] = inject('tags') || [];
  5. const userInfo: Member = inject('userInfo') || {
  6. id: 0,
  7. name: ''
  8. };
  9.  
  10. // 打印刚刚拿到的数据
  11. console.log(tags);
  12. console.log(tags.length);
  13. console.log(userInfo);
  14.  
  15. // 因为 2s 后数据会变,我们 3s 后再看下,能够看到已经是更新后的数据了
  16. setTimeout(() => {
  17. console.log(tags);
  18. console.log(tags.length);
  19. console.log(userInfo);
  20. }, 3000);
  21. }
  22. })
  23.  
  24. export default defineComponent({
  25. setup () {
  26. // 获取数据
  27. const tags: string[] = inject('tags') || [];
  28. const userInfo: Member = inject('userInfo') || {
  29. id: 0,
  30. name: ''
  31. };
  32.  
  33. // 打印刚刚拿到的数据
  34. console.log(tags);
  35. console.log(tags.length);
  36. console.log(userInfo);
  37.  
  38. // 因为 2s 后数据会变,我们 3s 后再看下,能够看到已经是更新后的数据了
  39. setTimeout(() => {
  40. console.log(tags);
  41. console.log(tags.length);
  42. console.log(userInfo);
  43. }, 3000);
  44. }
  45. })

??????
引用类型的数据,拿到后可以直接用,属性的值更新后,子孙组件也会被更新。
但是!!!由于不具备真正的响应性,return 给模板使用依然不会更新视图,如果涉及到视图的数据,请依然使用 响应式 API 。

7、基本类型的传递与接收 (针对非响应性数据的处理)

基本数据类型被直接 provide 出去后,再怎么修改,都无法更新下去,子孙组件拿到的永远是第一次的那个值。

先在 Grandfather.vue 里 provide 数据:

  1. export default defineComponent({
  2. // ...
  3. setup () {
  4. // provide 一个数组的长度
  5. const tags: string[] = [ '中餐', '粤菜', '烧腊' ];
  6. provide('tagsCount', tags.length);
  7.  
  8. // provide 一个字符串
  9. let name: string = 'Petter';
  10. provide('name', name);
  11.  
  12. // 2s 后更新数据
  13. setTimeout(() => {
  14. // tagsCount 在 Grandson 那边依然是 3
  15. tags.push('叉烧');
  16.  
  17. // name 在 Grandson 那边依然是 Petter
  18. name = 'Tom';
  19. }, 2000);
  20. }
  21. })

在 Grandsun.vue 里 inject 拿到数据:

  1. export default defineComponent({
  2. setup () {
  3. // 获取数据
  4. const name: string = inject('name') || '';
  5. const tagsCount: number = inject('tagsCount') || 0;
  6.  
  7. // 打印刚刚拿到的数据
  8. console.log(name);
  9. console.log(tagsCount);
  10.  
  11. // 因为 2s 后数据会变,我们 3s 后再看下
  12. setTimeout(() => {
  13. // 依然是 Petter
  14. console.log(name);
  15.  
  16. // 依然是 3
  17. console.log(tagsCount);
  18. }, 3000);
  19. }
  20. })

很失望,并没有变化。

??????
那么是否一定要定义成响应式数据或者引用类型数据呢?

当然不是,我们在 provide 的时候,也可以稍作修改,让它能够同步更新下去。

先在 Grandfather.vue 里 provide 数据:

  1. export default defineComponent({
  2. // ...
  3. setup () {
  4. // provide 一个数组的长度
  5. const tags: string[] = [ '中餐', '粤菜', '烧腊' ];
  6. provide('tagsCount', (): number => {
  7. return tags.length;
  8. });
  9.  
  10. // provide 字符串
  11. let name: string = 'Petter';
  12. provide('name', (): string => {
  13. return name;
  14. });
  15.  
  16. // 2s 后更新数据
  17. setTimeout(() => {
  18. // tagsCount 现在可以正常拿到 4 了
  19. tags.push('叉烧');
  20.  
  21. // name 现在可以正常拿到 Tom 了
  22. name = 'Tom';
  23. }, 2000);
  24. }
  25. })

再来 Grandsun.vue 里修改一下 inject 的方式,看看这次拿到的数据:

  1. export default defineComponent({
  2. setup () {
  3. // 获取数据
  4. const tagsCount: any = inject('tagsCount');
  5. const name: any = inject('name');
  6.  
  7. // 打印刚刚拿到的数据
  8. console.log(tagsCount());
  9. console.log(name());
  10.  
  11. // 因为 2s 后数据会变,我们 3s 后再看下
  12. setTimeout(() => {
  13. // 现在可以正确得到 4
  14. console.log(tagsCount());
  15.  
  16. // 现在可以正确得到 Tom
  17. console.log(name());
  18. }, 3000);
  19. }
  20. })

这次可以正确拿到数据了,看出这2次的写法有什么区别了吗?

??????
基本数据类型,需要 provide 一个函数,将其 return 出去给子孙组件用,这样子孙组件每次拿到的数据才会是新的。
但由于不具备响应性,所以子孙组件每次都需要重新通过执行 inject 得到的函数才能拿到最新的数据。

按我个人习惯来说,使用起来挺别扭的,能不用就不用……

由于不具备真正的响应性,return 给模板使用依然不会更新视图,如果涉及到视图的数据,请依然使用 响应式 API 。

到此这篇关于Vue3全局组件通信之provide / inject的文章就介绍到这了,更多相关Vue3全局组件通信内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号