Vue3 provide/inject 原理

132 阅读3分钟

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 原理

  1. 获取当前组件实例
  2. 创建当前组件的provides对象,以父级的provides作为原型
  3. 为当前组件的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 原理

  1. 获取当前组件实例
  2. 返回父组件的provides,对于根组件返回appContext的provides
  3. 若找不到父组件的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
    }
  }
}

当前组件实例怎么获取?

  1. 渲染组件(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
}
  1. 初始化组件状态时需要执行setup,执行setup前保存组件实例,执行完毕后释放保存的实例
let currentInstance=null

setCurrentInstance()
// 执行setup
const setupResult=callWithErrorHandling(setup,instance)
unSetCurrentInstance()

function setCurrentInstance(instance){
    currentInstance=instance
}
function unSetCurrentInstance(){
    currentInstance=null
}

注意:provide需要和组件setup同步执行才可以确保获取组件实例。