provide, inject, InjectionKey use

provide and inject mainly provide use cases for higher order plugin/component libraries. It is not recommended to be used directly in application code.

Definition Description: This pair of options is used together. To allow an ancestor component to inject a dependency to all its descendants, no matter how deep the component hierarchy is, and it will always take effect when the upstream and downstream relationships are established.
In layman's terms, there are too many levels of component introduction, and our descendant components want to obtain the resources of ancestor components, so what should we do? We can't always take the parent level up, and the code structure is easy to be confused. This is what this pair of options does.

The implementation principle of Provide / Inject is actually realized by clever use of prototype and prototype chain (EventBus event)

Realization principle

import {  getCurrentInstance } from 'vue'
function provide(key, value) {
    // Get the current component instance
    const currentInstance: any = getCurrentInstance()
    if(currentInstance) {
        // Get the provides attribute on the current component instance
        let { provides } = currentInstance
        // Get the provides property of the current parent component
        const parentProvides = currentInstance.parent.provides
        // If the current provides is the same as the parent's provides, it means that no value has been assigned
        if(provides === parentProvides) {
            // Object.create() Another way of creating objects in es6 can be understood as inheriting an object, and the added properties are under the prototype.
            provides = currentInstance.provides = Object.create(parentProvides)
        }
        provides[key] = value
    }
}

/*------------------------------------------------------------------------------------  */
function inject(key: any, defaultValue: any, treatDefaultAsFactory = false) {
  // Get the current component instance object
  const instance: any = getCurrentInstance()
  if (instance) {
    // If the intance is located in the root directory, return to the provides of the appContext, otherwise return the provides of the parent component
    const provides =
      instance.parent == null
        ? instance.vnode.appContext && instance.vnode.appContext.provides
        : instance.parent.provides

      //If the key exists in the provides, it will be output
    if (provides && key in provides) {
      return provides[key]

      //Otherwise the default value will be output
    } else if (arguments.length > 1) {
      // If more than 1 parameter exists
      return treatDefaultAsFactory
        // If the default content is a function, execute it and bind the proxy object of the component instance to this of the function through the call method
        ? defaultValue.call(instance.proxy)
        : defaultValue
    }
  }
}

**provide:** is used to provide values ‚Äč‚Äčthat can be injected by descendant components.
provide( name,value )
name: Defines the name of the provided property.
value : the value of the property.

import {  provide } from 'vue'
 // provide out
provide('msg', msg);

inject: Used to declare the attributes to be matched and injected into the current component from the upper provider.
inject(name,default)
name: Receive the attribute name provided by provide.
default: Set the default value, you can leave it blank, it is an optional parameter.

import { inject } from "vue"

const info = inject('msg')

Example (to add reactivity to provide/inject, use ref or reactive .):
parent component

//parent component code
 
<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("Did you study today?")
 
    setTimeout(()=>{
 
      info.value = "Don't make excuses, learn now"
 
    },2000)
 
    provide('info',info)
 
    return{
 
      info
 
    }
 
  },
 
  components:{
 
    InjectCom
 
  }
 
}
 
</script>

subcomponent code

// InjectCom subcomponent code
 
<template>
 
 {{info}}
 
</template>
 
<script>
 
import { inject } from "vue"
 
export default {
 
  setup(){
 
    const info = inject('info')
 
    setTimeout(()=>{
 
      info.value = "renew"
 
    },2000)
 
    return{
 
      info
 
    }
 
  }
 
}
 
</script>

InjectionKey

Using provide and inject can easily communicate between parent and child components, even if it is a multi-layer child component, you can also get the value of the parent component. However, there will be type problems, which will cause the method passed from the parent component to the child component to fail to call. At this time, we can use the InjectionKey provided by vue3 to solve this problem.

  1. Define objects and functions in the parent component and expose them with provide
import { provide, reactive, ref } from 'vue'
const msg = ref<String>("i am your grandpa!")
const obj = reactive<Record<string, any>>({
    id: 1,
    value: "Ha ha"
})
const sendMsg = (): void => {
    console.log("Hahaha,,grandson!");
}

provide('msg', msg);
provide('obj', obj);
provide('sendMsg', sendMsg);
  1. grandson component
<template>
    <div>news from grandpa:{{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)
Receiving with inject, msg and obj can be used directly,
But you can see that the received function is unknown, which makes the function unable to be called

solution:
InjectionKey to define the type to ensure that the value passed by the parent is the same as the value type received by the child

  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. parent component
import { provide, reactive, ref } from 'vue'

import { objKey, sendMsgKey } from "@/views/home/type.ts"



const msg = ref<String>("i am your grandpa!")
const obj = reactive<Record<string, any>>({
    id: 1,
    value: "Ha ha"
})
function sendMsg() {
    console.log("Hahaha,grandson!");
}


provide('msg', msg);
provide(objKey, obj);
provide(sendMsgKey, sendMsg);
  1. Subassembly
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?.()

Suggestion: inject must have a default value to prevent BUG

Tags: Front-end Vue Javascript Vue.js TypeScript

Posted by theweirdone on Wed, 07 Dec 2022 07:17:19 +1030