provide、inject、InjectionKey使用

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解决这一问题。

  1. 在父组件中定义对象和函数 ,用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);
  1. 孙子组件
<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来定义类型,确保父传出去的值和子接收到的值类型是一样的

  1. 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')
  1. 父组件

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);
  1. 子组件
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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值