provide/inject 介绍
父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
provide
要为组件后代提供数据,需要使用到 provide()函数:
<script setup>
import { provide } from 'vue'
provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>
后代组件会用注入名来查找期望注入的值。
应用层的provide
在应用级别提供的数据在该应用内的所有组件中都可以注入。
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
inject
要注入上层组件提供的数据,需使用 inject() 函数:
vue
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
需要确保 provide/inject 是在 setup() 中同步调用的,以便在provide/inject中获取当前组件实例。
如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。这使得注入方组件能够通过 ref 对象保持了和供给方的响应性链接。
provide 原理
- 获取当前组件实例
- 创建当前组件的provides对象,以父级的provides作为原型
- 为当前组件的provides赋值
组件渲染时会把父级的provides往下层层传递给子组件的实例,所以当前组件provides的初始值与父组件的provides是相等的。
export function provide(key, value) {
// 获取当前组件实例
const currentInstance = getCurrentInstance()
if(currentInstance) {
// 获取当前组件实例上provides属性
let { provides } = currentInstance
// 获取当前父级组件的provides属性
const parentProvides = currentInstance.parent.provides
// 如果当前的provides和父级的provides相同则说明还未赋值
if(provides === parentProvides) {
// 创建当前组件的provides对象,以父级provides作为原型
// 这样inject可以通过原型链查找对应属性
provides = currentInstance.provides = Object.create(parentProvides)
}
// 为组件当前的provides赋值
provides[key] = value
}
}
inject 原理
- 获取当前组件实例
- 返回父组件的provides,对于根组件返回appContext的provides
- 若找不到父组件的provides且传了2个参数,返回默认值
export function inject(
key,
defaultValue,
treatDefaultAsFactory = false
) {
// 获取当前组件实例对象
const instance = currentInstance || currentRenderingInstance
if (instance) {
// 如果intance是根组件,则返回到appContext的provides,否则就返回父组件的provides
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) {
// 如果存在1个参数以上
return treatDefaultAsFactory && isFunction(defaultValue)
// 如果默认内容是个函数的,就执行并且通过call方法把组件实例的代理对象绑定到该函数的this上
? defaultValue.call(instance.proxy)
: defaultValue
}
}
}
当前组件实例怎么获取?
- 渲染组件(mountComponent)时先创建组件实例,并且把父级的provides传给当前组件实例。
appContext、provides都是创建实例时从父组件向子组件往下层层传递。
function createComponentInstance(vnode,parnet){
const appContext=(parent?parent.appContext:vnode.appContext)||createAppContext()
const instance={
vnode,
parent,
appContext,
provides:parent?parent.provides:Object.create(appContext.provides)
}
return instance
}
- 初始化组件状态时需要执行setup,执行setup前保存组件实例,执行完毕后释放保存的实例
let currentInstance=null
setCurrentInstance()
// 执行setup
const setupResult=callWithErrorHandling(setup,instance)
unSetCurrentInstance()
function setCurrentInstance(instance){
currentInstance=instance
}
function unSetCurrentInstance(){
currentInstance=null
}
注意:provide需要和组件setup同步执行才可以确保获取组件实例。