Vue 3.0 Provide和Inject实现共享数据

ProvideInject可以在祖(父)组件和子(孙)组件间实现传值。相比prop只能父子之间传值而言,ProvideInject传值更方便,相比vuex又更轻量。

接下来我们就从使用和实现原理两方面来介绍ProvideInject

ProvideInject的使用

概括: 祖(父)组件中使用一个provide函数来提供数据,而子(孙)组件使用inject函数来获取数据。

provide API 的使用

我们这里就用官方的例子, 阅读过官方例子的童鞋可以跳过本节。

  • 使用前需要先从vue中引用provide函数
import { provide } from "vue";

使用插件后,在敲完provide代码后编辑器(例如VS Code)可以自动引入这个函数,不需要手动引入。这里只是为了说明。

  • setup函数中调用provide函数提供数据
<!-- GrandParent.vue -->
setup() {
    // 省略其他...
    provide('location', '北极')
    provide('geolocation', {
      longitude: 90,
      latitude: 135
    })
}

provide有两个参数:第一个参数表示字符串类型的key;第二个参数为value,,可以是对象,也可以是普通数据类型.

inject API 的使用
  • 使用前需要先从vue中引用inject函数
import { inject } from "vue";
  • setup函数中调用inject函数获取数据
<!-- GrandSon.vue -->
setup() {
    // 省略其他...
    const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')
    
  },

inject也有两个参数:第一个参数表示需要获取的key;第二个参数为key未取到值时候填充的的默认值(非必选参数)。如果没有设置默认值,则没有取到值则为undefined

provide添加响应性

添加响应性是指provide提供的数据发生变化时,inject使用数据的组件需要进行刷新界面。我们想到肯定得需要Vue3.0的一些响应式的API

  • 修改后的provide样子如下:
<!-- GrandParent.vue -->
setup() {
    // 省略其他...
    provide('location', ref('北极'))
    provide('geolocation', reactive({
      longitude: 90,
      latitude: 135
    }))
}
  • inject的使用没有差别,但是如果使用inject的组件模板中用到provide提供的数据,则组件会及时进行UI刷新。

ProvideInject的原理分析

传值

  • 组件实例对象ComponentInternalInstance有一个provides属性;
<!-- components.ts -->
export interface ComponentInternalInstance {
  **
   * Object containing values this component provides for its descendents
   * @internal
   */
  provides: Data
}
  • 在创建组件实例对象时候,provides属性会指向父组件实例对象的provides属性;
export function createComponentInstance(
  vnode: VNode,
  parent: ComponentInternalInstance | null,
  suspense: SuspenseBoundary | null
) {

  const instance: ComponentInternalInstance = {
    // 省略其他属性...
    provides: parent ? parent.provides : Object.create(appContext.provides),
  }
  
  return instance
}

根实例对象的providesObject.create(null),也就是纯净的空对象{}.

当所有组件都没有使用provide函数时, 效果如下图所示:

provide
  • 在组件实例调用provide函数时,会将父组件的provides为模板拷贝一份做为当前组件的provides,不再指向父组件的provides,然就将provide中的keyvalue对应保存起来;
export function provide<T>(key: InjectionKey<T> | string | number, value: T) {
  let provides = currentInstance.provides
  const parentProvides =
      currentInstance.parent && currentInstance.parent.provides
  // 如果指向父组件的`provides`对象则拷贝一份
  if (parentProvides === provides) {
    provides = currentInstance.provides = Object.create(parentProvides)
  }
  // 将`provide`的`key`和`value`对应保存起来
  provides[key as string] = value
}

当某个组件有使用provide函数时, 效果如下图所示:

provide

思考题: 如果(祖)父和子(孙)组件provide函数使用了相同的key来提供数据,那他们的共同子(孙)组件用inject函数获取到的数据value是哪个值呢?

答案:很明显是离组件自身最近的(祖)父组件的provide提供的value

  • inject函数的逻辑就非常简单了,就是取当前组件实例的provides对象对应keyvalue, 如果没取到value就用默认值defaultValue,如果没有默认值defaultValue结果就是undefined;
function inject(key, defaultValue, treatDefaultAsFactory = false) {
  const instance = currentInstance || currentRenderingInstance;
  if (instance) {
      // 取值
      const provides = instance.parent == null
          ? instance.vnode.appContext && instance.vnode.appContext.provides
          : instance.parent.provides;
      if (provides && key in provides) {
          return provides[key];
      }
      else if (arguments.length > 1) {
          return treatDefaultAsFactory && isFunction(defaultValue)
              ? defaultValue.call(instance.proxy)
              : defaultValue;
      }
  }
}

响应式

这个逻辑其实不用过多分析了,和在本组件内申明一个响应式数据是没有差别的,可参考前面的文章。响应式数据变化后会触发组件的副作用渲染函数更新UI。

ProvideInject的缺陷

ProvideInject进行传值的情况下,祖(父)组件子(孙)组件间是相互独立的,也就是说祖(父)组件不关心是否有子(孙)组件使用其提供的数据,子(孙)组件 也不知道数据来自于哪个祖(父)组件

数据是隔离了,但是会造成组件层级的高度耦合,例如子(孙)组件的正常运行必须要对应的祖(父)组件提供数据。否则就会功能异常。

所以ProvideInject 比较适合在功能库中使用(本来组件耦合度就很高的场景),而不是大型项目中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容