vue3中provide/inject实现原理
导读
Vue有一个注入全局上下文的机制,叫provide/inject
,其可以向子级组件注入一些属性,无论嵌套得有多深。其效果和React的Context
类似。本文将通过具体的源码详细说明其原理实现。
下方所述代码在 /core/packages/runtime-core/src/apiInject.ts
provide
provide
接受两个参数,key和value,也就是注入数据的key值和具体注入的数据,key可接受string和Symbol类型。
先贴出源码
1 | export function provide<T, K = InjectionKey<T> | string | number>( |
可以看到,组件上有个属性provides
,最终开发者使用provide
注入的数据都会设置在其身上,从``provide开始往下的组件树,子组件使用
provide时将同样使用父级组件的
provides属性来创建自己的
provides`。
在这段代码中,需要注意的是Object.create()
的使用。其传入一个现有的对象作为原型,创建一个新的对象。也就是说子级组件的provides
从父级继承而来,是一个新的对象,保留父级组件provides
属性的同时,也不会污染到父级组件provides
,实现了隔离。但请注意:子组件的provides
在创建时直接复用父级组件的provides
或应用上下文对象的provides
(当组件是根组件时),也就是按需隔离,也就是使用provide
时才使用Object.create()
通过原型链实现隔离,否则每一子组件从创建开始就创建自己的provides
,随着组件嵌套,这个原型链也将增长,进一步影响到性能。
inject
inject
接受三个参数,第一个参数就是provide
使用的key,第二个就是上游父组件没有使用provide
提供数据时默认的值,第三个则是是否默认第二个参数是函数。
1 | export function inject( |
首先通过currentInstance
拿到组件渲染实例,同时,由于vue提供了函数式组件的的能力,将通过currentRenderingInstance
拿到组件实例。通过if(instance || currentApp)
判断当前是否有组件实例,如果没有就观察是否使用app.runWithContext
注入了上下文,如果注入了currentApp
有值。而app.runWithContext
通常和createApp()
使用有关。
1 | import {createApp} from "vue" |
这里可以看看笔者之前的一篇文章:Cesium+Vue3实现可跟踪的点位详情弹窗,其就是利用到createApp()
这个函数。
1 | const provides = currentApp |
上述的一大串的三元表达式,当createApp
存在,此时是一个应用的概念,其组件components
、provides
、exports
属性都保存在内部的上下文属性_context
上,取出provides
值;当组件示例存在但是没有父组件,意味着其为根组件,那么就从应用的上下文对象appContext
取出provides。往下的逻辑就是取出provides
对应的属性,如果没有属性且提供了默认值的情况,则使用默认值,否则返回的就是undefined
。当然,在开发模式下,如果找不到也没有默认值,就打印警告。
总结
provide/inject
给Vue提供了上下文对象的能力,无论组件嵌套多么深。其原理就是在组件实例或者上下文对象上设置了provides
属性,子级组件创建时将复用父级组件的provides
,并在需要时通过Object.create()
原型继承父级provides
。消费provides
对象上的数据时,通过inject
取出。