这位同学说一说 vue3 的 Provide、Inject

Provide、Inject 可以帮助我们解决 "prop 逐级透传" 的问题,那什么是 "prop 逐级透传" 呢?

如下图 <Root> 想要把参数传递到 <DeepChild> 的时候 <Footer> 可能根本不关心这些 props 是什么,但为了使 <DeepChild> 能访问到它们,仍然需要定义并向下传递

如果组件链路非常长,可能会影响到更多这条路上的组件。这一问题被称为"prop 逐级透传"

(官网的图画的很好,现在是我的了)

Provide、Inject 则可以帮助我们解决这一问题

Provide、Inject 一般是成对出现的,由 Provide 在组件中提供数据、使用 Inject 在其任何后代的组件树,无论层级有多深 都可以获取由 Provide 提供的数据(Provide 提供的不一定是数据也可以是函数)

总结:Provide、Inject 常于祖先组件与后代组件之间的通信,但不适用于任意兄弟组件间通信。

Provide、Inject API

Inject 无法无法获取到同一组件内 Provide 提供的值但我想也没人会这么写吧!🤪

Provide

提供一个值,可以被后代组件注入

ts 复制代码
function provide<T>(key: InjectionKey<T> | string, value: T): void

provide 第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值

建议使用 symbol 来定义 key 并将 key 放在一个单独的文件中,这样它就可以被多个组件导入

ts 复制代码
<script setup>
import { ref, provide } from 'vue'
import { countSymbol } from './injectionSymbols'

// 提供静态值
provide('path', '/project/')

// 提供响应式的值
const count = ref(0)
provide('count', count)

// 提供时将 Symbol 作为 key
provide(countSymbol, count)
</script>

Inject

注入一个由祖先组件或整个应用 (通过 app.provide()) 提供的值

ts 复制代码
// 没有默认值
function inject<T>(key: InjectionKey<T> | string): T | undefined

// 带有默认值
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T

// 使用工厂函数
function inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: () => T,
  treatDefaultAsFactory: true
): T

第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会"覆盖"链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject() 将返回 undefined,除非提供了一个默认值。

inject 不设置默认值,拿不到数据返回 undefined, 设置默认值,拿不到数据返回 "设置默认值"

对某些创建起来比较复杂的值 inject 第二个参数可以是一个工厂函数但同时第三个参数需要设置为 true

ts 复制代码
<script setup>
import { inject } from 'vue'
import { countSymbol } from './injectionSymbols'

// 注入不含默认值的静态值
const path = inject('path')

// 注入响应式的值
const count = inject('count')

// 通过 Symbol 类型的 key 注入
const count2 = inject(countSymbol)

// 注入一个值,若为空则使用提供的默认值
const bar = inject('path', '/default-path')

// 注入一个值,若为空则使用提供的函数类型的默认值
const fn = inject('function', () => {})

// 注入一个值,若为空则使用提供的工厂函数
const baz = inject('factory', () => new ExpensiveObject(), true)
</script>

为 provide / inject 标注类型

官网写的比我好,看官网!

Provide、Inject 源码阅读

看完使用了,上点强度看下源码是如何实现的!

源码位置 packages/runtime-core/src/apiInject.ts provide、inject 写在同一个文件内

先来看下文件中引入的一些依赖

ts 复制代码
import { isFunction } from '@vue/shared'
import { currentInstance } from './component'
import { currentRenderingInstance } from './componentRenderContext'
import { currentApp } from './apiCreateApp'
import { warn } from './warning'
  1. isFunction:判断传入的值是否是一个函数
  2. currentInstance:表示当前正在执行 setup() 函数的组件实例,在组件的 setup() 函数执行期间会被设置
  3. currentRenderingInstance:表示当前正在渲染的组件实例,主要用于函数式组件的上下文中,在组件的 render 函数执行期间会被设置
  4. currentApp :当前正在运行的 Vue 应用实例,在调用 createApp() 创建 1.warn:控制台警告日志输出

Provide

ts 复制代码
interface InjectionConstraint<T> {}

export type InjectionKey<T> = symbol & InjectionConstraint<T>

/**
 * provide 函数用于提供可注入的依赖
 * @param key - 依赖的键,可以是 symbol、字符串或数字
 * @param value - 要提供的值
 */
export function provide<T, K = InjectionKey<T> | string | number>(
  key: K,
  value: K extends InjectionKey<infer V> ? V : T,
): void {
  if (!currentInstance) {
    if (__DEV__) {
      warn(`provide() can only be used inside setup().`)
    }
  } else {
    /** 官方注释
     * 默认情况下,组件实例继承其父组件的 provides 对象
     * 但当需要提供自己的值时,会创建一个新的 provides 对象
     * 并将父组件的 provides 对象作为原型
     * 这样在 inject 中可以直接查找直接父组件的注入,并让原型链完成剩下的工作
     */

    // 获取当前实例的 provides
    let provides = currentInstance.provides

    // 获取父组件的 provides
    // 这里可能的值是 null or 一个对象
    const parentProvides = currentInstance.parent && currentInstance.parent.provides

    // 如果一致就表示这是当前组件第一次调用 provide 提供数据
    // 使用 parentProvides 创建一个新的 provides 对象
    // 这样既可以子组件不会修改父组件的 provides,又可以让子组件可以访问父组件的 provides (需要理解 js 原型、对象引用)
    if (parentProvides === provides) {
      // provides 和 currentInstance.provides 指向同一个对象,当修改这个对象的属性时,无论通过哪个引用修改,都会反映在这个对象上
      provides = currentInstance.provides = Object.create(parentProvides)
    }

    // TS 不允许使用 symbol 作为索引类型,所以这里转换为 string
    provides[key as string] = value
  }
}

因为 providescurrentInstance.provides 指向的是同一个对象,- 所以当我们修改 provides 对象时,currentInstance.provides 也会同步更新

Inject

  1. currentApp._context.provides: 应用上下文的 provides
  2. instance.parent:当前组件的父组件实例,组件挂载时确定
  3. instance.vnode.appContext:Vue 应用的根级上下文对象本质上与 `currentApp._context 相同
ts 复制代码
// 为 inject 函数定义多个重载签名,以支持不同的使用场景
export function inject<T>(key: InjectionKey<T> | string): T | undefined
export function inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: T,
  treatDefaultAsFactory?: false,
): T
export function inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: T | (() => T),
  treatDefaultAsFactory: true,
): T

/**
 * inject 函数用于注入依赖
 * @param key - 要注入的依赖的键
 * @param defaultValue - 默认值,当找不到注入值时使用
 * @param treatDefaultAsFactory - 是否将默认值作为工厂函数处理
 */
export function inject(
  key: InjectionKey<any> | string,
  defaultValue?: unknown,
  treatDefaultAsFactory = false,
) {
  // 获取当前实例,如果在函数式组件中,则回退到 currentRenderingInstance
  const instance = currentInstance || currentRenderingInstance

  // 同时支持通过 app.runWithContext() 查找应用级别的 provides
  if (instance || currentApp) {
    // 确定 provides 来源:
    // 1. 如果存在 currentApp,使用应用上下文的 provides
    // 2. 否则,如果是根组件实例,使用 vnode 的 appContext provides
    // 3. 否则,使用父组件的 provides
    const provides = currentApp
      ? currentApp._context.provides
      : instance
        ? instance.parent == null
          ? instance.vnode.appContext && instance.vnode.appContext.provides
          : instance.parent.provides
        : undefined

    if (provides && (key as string | symbol) in provides) {
      // 如果在 provides 中找到对应的 key,返回其值
      return provides[key as string]

    } else if (arguments.length > 1) {
      // 如果提供了默认值
      return treatDefaultAsFactory && isFunction(defaultValue)
        ? defaultValue.call(instance && instance.proxy) // 如果默认值是工厂函数,则调用它
        : defaultValue // 否则直接返回默认值

    } else if (__DEV__) {
      // 如果既没有获取到对应的 key 的数据又没有默认值控制台抛出警告
      warn(`injection "${String(key)}" not found.`)
    }

  } else if (__DEV__) {
    warn(`inject() can only be used inside setup() or functional components.`)
  }
}

参考

官网-依赖注入

官网-Provide、Inject API

官网-为 provide / inject 标注类型

文中部分代码来自官网,我宣布 vue 官网是写的最棒的!

相关推荐
Wyc7240928 分钟前
HTML:入门
前端·html
Sunny_lxm29 分钟前
自定义列甘特图,原生开发dhtmlxgantt根特图,根据数据生成只读根特图,页面展示html demo
前端·html·甘特图·dhtmlxgantt
熊猫钓鱼>_>1 小时前
建筑IT数字化突围:建筑设计企业的生存法则重塑
前端·javascript·easyui
GISer_Jing3 小时前
前端性能指标及优化策略——从加载、渲染和交互阶段分别解读详解并以Webpack+Vue项目为例进行解读
前端·javascript·vue
不知几秋3 小时前
数字取证-内存取证(volatility)
java·linux·前端
水银嘻嘻5 小时前
08 web 自动化之 PO 设计模式详解
前端·自动化
Zero1017137 小时前
【详解pnpm、npm、yarn区别】
前端·react.js·前端框架
&白帝&7 小时前
vue右键显示菜单
前端·javascript·vue.js
Wannaer7 小时前
从 Vue3 回望 Vue2:事件总线的前世今生
前端·javascript·vue.js
羽球知道7 小时前
在Spark搭建YARN
前端·javascript·ajax