Libon

Vue3 provide/inject 源码实现

4 分钟 #vue
学习 vue 中的 provide/inject 方法是如何被实现的

ToC

hasInjectionContext()

hasInjectionContext() 方法主要用于判断当前是否具有注入上下文环境, currentInstancecurrentRenderingInstance 都是指组件,currentApp 则表示的是应用根组件,如果没有获取到 currentInstance 则判断根应用实例是否初始化:

1
export function hasInjectionContext(): boolean {
2
return !!(currentInstance || currentRenderingInstance || currentApp)
3
}

provide()

provide() 函数的实现比较简单,删除ts类型及原代码注释,再加上我自己的理解以后代码如下:

1
export function provide(key, value) {
2
// 如果没有获取到当前的组件实例则表示在组件外调用的 provide()
3
if (!currentInstance) {
4
// 为了减少生产包的体积,将提示设置为只在开发环境打印
5
// 打包生产包时, __DEV__ 变量会被替换为 false, 这样就会被 tree-shanking 丢弃
6
if (__DEV__) {
7
warn(`provide() can only be used inside setup().`)
8
}
9
} else {
16 collapsed lines
10
// 先获取当前组件的 provides
11
let provides = currentInstance.provides
12
13
// 再尝试获取父级组件的 provides
14
const parentProvides = currentInstance.parent && currentInstance.parent.provides
15
16
// 如果他们相等,则可能是在创建页面级组件,将父级的provides覆盖当前的 provides
17
// 确保所有子组件都是用的最上层的 provides,保证他们的引用相同,从而实现无限层级的上下文透传
18
if (parentProvides === provides) {
19
provides = currentInstance.provides = Object.create(parentProvides)
20
}
21
22
// 获取到 provides 以后将当前的值增加上去
23
provides[key] = value
24
}
25
}

inject()

inject() 在实现上会比 provide() 稍加复杂一点,但也没有太复杂,多的那部分判断只是用来获取当前组件上下文的代码而已,同样的,删除ts类型及原注释,加上我自己的代码理解以后代码如下:

1
export function inject(key, defaultValue, treatDefaultAsFactory = false) {
2
// 回退到 `currentRenderingInstance`,以便可以在功能组件中调用它
3
const instance = currentInstance || currentRenderingInstance
4
5
// 如果找不到当前正在渲染的组件,那就从根应用上获取
6
if (instance || currentApp) {
7
// https://github.com/vuejs/core/issues/2400
8
// 支持在 app.use 的插件里使用 inject,如果找不到则一直向上回退,直到根应用app
9
const provides = instance
25 collapsed lines
10
? instance.parent == null
11
? instance.vnode.appContext && instance.vnode.appContext.provides
12
// 因为父级的provides会一路将其组件级的provides作为原型,所以只要找父级的就能拿到所有的 provide item
13
: instance.parent.provides
14
: currentApp!._context.provides
15
16
// 如果是正常提供的 provides,并且找到了对应的key则正常返回
17
if (provides && key in provides) {
18
return provides[key]
19
} else if (arguments.length > 1) {
20
// 如果参数位大于1,同时 treatDefaultAsFactory 设置为true,则表示 defaultValue 可能是个功能函数
21
return treatDefaultAsFactory && isFunction(defaultValue)
22
// 如果是函数,则将当前获取到的组件实例当作this指向获取它的返回值
23
? defaultValue.call(instance && instance.proxy)
24
// 如果没有设置 treatDefaultAsFactory 则直接返回 defaultValue, 没有设置则 undefined
25
: defaultValue
26
} else if (__DEV__) {
27
// 如果没有找到则在开发环境下提示未找到
28
warn(`injection "${String(key)}" not found.`)
29
}
30
} else if (__DEV__) {
31
// 没有找到组件实例则表示可能是在组件外部调用的方法
32
warn(`inject() can only be used inside setup() or functional components.`)
33
}
34
}

总结

这就是这两个API的全部实现,整体看下来还是很简单的,provide() 就是一直继承父级的 provides 作为自己的,利用原型链的特性一直向上查找,inject() 则是获取当前的组件实例,然后从实例上获取 provides,如果没有找到则在开发环境打印警告,hasInjectionContext() 方法则是判断当前组件/根应用是否已经挂载,都没有的话则表示应用没有初始化,不具备调用 provide()inject() 的条件。

以上。


CD ..