provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
定义说明:这对选项是一起使用的。以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
通俗的说就是:组件得引入层次过多,我们的子孙组件想要获取祖先组件得资源,那么怎么办呢,总不能一直取父级往上吧,而且这样代码结构容易混乱。这个就是这对选项要干的事情。
Provide / Inject 的实现原理其实就是巧妙利用了原型和原型链(EventBus事件)来实现的
实现原理
import { getCurrentInstance } from 'vue'
function provide(key, value) {
// 获取当前组件实例
const currentInstance: any = getCurrentInstance()
if(currentInstance) {
// 获取当前组件实例上provides属性
let { provides } = currentInstance
// 获取当前父级组件的provides属性
const parentProvides = currentInstance.parent.provides
// 如果当前的provides和父级的provides相同则说明还没赋值
if(provides === parentProvides) {
// Object.create() es6创建对象的另一种方式,可以理解为继承一个对象, 添加的属性是在原型下。
provides = currentInstance.provides = Object.create(parentProvides)
}
provides[key] = value
}
}
/*------------------------------------------------------------------------------------ */
function inject(key: any, defaultValue: any, treatDefaultAsFactory = false) {
// 获取当前组件实例对象
const instance: any = getCurrentInstance()
if (instance) {
// 如果intance位于根目录下,则返回到appContext的provides,否则就返回父组件的provides
const provides =
instance.parent == null
? instance.vnode.appContext && instance.vnode.appContext.provides
: instance.parent.provides
//key 存在provides中就输出
if (provides && key in provides) {
return provides[key]
//否则就会输出默认值
} else if (arguments.length > 1) {
// 如果存在1个参数以上
return treatDefaultAsFactory
// 如果默认内容是个函数的,就执行并且通过call方法把组件实例的代理对象绑定到该函数的this上
? defaultValue.call(instance.proxy)
: defaultValue
}
}
}
**provide:**用于提供可以被后代组件注入的值。
provide( name,value )
name:定义提供 property 的 name 。
value :property 的值。
import { provide } from 'vue'
// provide出去
provide('msg', msg);
inject:用于声明要通过从上层提供方匹配并注入进当前组件的属性。
inject(name,default)
name:接收 provide 提供的属性名。
default:设置默认值,可以不写,是可选参数。
import { inject } from "vue"
const info = inject('msg')
示例(为了给 provide/inject 添加响应性,使用 ref 或 reactive 。):
父组件
//父组件代码
<template>
<div>
info:{{info}}
<InjectCom ></InjectCom>
</div>
</template>
<script>
import InjectCom from "./InjectCom"
import { provide,readonly,ref } from "vue"
export default {
setup(){
let info = ref("今天你学习了吗?")
setTimeout(()=>{
info.value = "不找借口,立马学习"
},2000)
provide('info',info)
return{
info
}
},
components:{
InjectCom
}
}
</script>
子组件 代码
// InjectCom 子组件代码
<template>
{{info}}
</template>
<script>
import { inject } from "vue"
export default {
setup(){
const info = inject('info')
setTimeout(()=>{
info.value = "更新"
},2000)
return{
info
}
}
}
</script>
InjectionKey
用provide和inject可以很方便的在父子组件之间通讯,即使是多层子组件,也能获取到父组件的值。但是会遇上类型上的问题,导致父组件传给子组件的方法无法调用,这时候我们可以使用vue3提供的InjectionKey解决这一问题。
- 在父组件中定义对象和函数 ,用provide暴露
import { provide, reactive, ref } from 'vue'
const msg = ref<String>("我是你爷爷!")
const obj = reactive<Record<string, any>>({
id: 1,
value: "哈哈"
})
const sendMsg = (): void => {
console.log("哈哈哈,,孙子!");
}
provide('msg', msg);
provide('obj', obj);
provide('sendMsg', sendMsg);
- 孙子组件
<template>
<div>爷爷的消息:{{msg}}{{obj}}</div>
</template>
<script setup lang='ts'>
import { inject } from 'vue'
const msg = inject('msg')
const obj = inject('obj')
const setMsg = inject('setMsg')
setMsg()
</script>
![image.png](https://img-blog.csdnimg.cn/img_convert/fbb1482a57c57432ffa3d86fd7fec60f.png#clientId=u6e9da93a-42bc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=311&id=u90c75a18&margin=[object Object]&name=image.png&originHeight=545&originWidth=738&originalType=binary&ratio=1&rotation=0&showTitle=false&size=64539&status=done&style=none&taskId=u331b2737-d0dd-4665-92ce-5c632dfeeb1&title=&width=421.7142857142857)
用inject接收,msg和obj,是可以直接使用的,
但是可以看到接收到的函数是unknown,导致函数无法调用
解决方案:
InjectionKey来定义类型,确保父传出去的值和子接收到的值类型是一样的
- type.ts
import { InjectionKey } from 'vue'
export interface objType {
id: number,
value: string
}
export interface User {
name: string
age: number
}
export const objKey: InjectionKey<objType> = Symbol('objKey')
export type sendMsg = () => void
export const sendMsgKey: InjectionKey<sendMsg> = Symbol('objKey')
- 父组件
import { provide, reactive, ref } from 'vue'
import { objKey, sendMsgKey } from "@/views/home/type.ts"
const msg = ref<String>("我是你爷爷!")
const obj = reactive<Record<string, any>>({
id: 1,
value: "哈哈"
})
function sendMsg() {
console.log("哈哈哈,孙子!");
}
provide('msg', msg);
provide(objKey, obj);
provide(sendMsgKey, sendMsg);
- 子组件
import { inject } from 'vue'
import { objKey, sendMsgKey } from "@/views/home/type.ts"
const msg = inject('msg',undefined)
const obj = inject(objKey,undefined)
const setMsg = inject(sendMsgKey, () => { })
setMsg?.()
建议:inject 都要带有默认值,防止BUG