赞
踩
父组件中提供数据,并在子组件中注入这些数据,从而实现了组件之间的数据传递。简单来说就是父组件向子组件传值的一个方式。
vue3官网对依赖注入解决的问题这样介绍的:
通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。想象一下这样的结构:有一些多层级嵌套的组件,形成了一颗巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦:
注意,虽然这里的 <Footer>
组件可能根本不关心这些 props,但为了使 <DeepChild>
能访问到它们,仍然需要定义并向下传递。如果组件链路非常长,可能会影响到更多这条路上的组件。这一问题被称为“prop 逐级透传”,显然是我们希望尽量避免的情况。
provide
和 inject
可以帮助我们解决这一问题。 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
这可以说就是provide和inject的来源,为了解决Prop 逐级透传问题的问题
这里部分也采用的官网的介绍,讲的很清楚
提供一个值,可以被后代组件注入。
类型
function provide<T>(key: InjectionKey<T> | string, value: T): void
详细信息
provide()
接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。
当使用 TypeScript 时,key 可以是一个被类型断言为 InjectionKey
的 symbol。InjectionKey
是一个 Vue 提供的工具类型,继承自 Symbol
,可以用来同步 provide()
和 inject()
之间值的类型。
与注册生命周期钩子的 API 类似,provide()
必须在组件的 setup()
阶段同步调用。
示例
<script setup>
import { ref, provide } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 提供静态值
provide('foo', 'bar')
// 提供响应式的值
const count = ref(0)
provide('count', count)
// 提供时将 Symbol 作为 key
provide(fooSymbol, count)
</script>
注入一个由祖先组件或整个应用 (通过 app.provide()
) 提供的值。
类型
// 没有默认值
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(这个key就是用来和provide设定的第一个参数进行匹配)。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject()
将返回 undefined
,除非提供了一个默认值。
第二个参数是可选的,即在没有匹配到 key 时使用的默认值。
第二个参数也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。在这种情况下,你必须将 true
作为第三个参数传入,表明这个函数将作为工厂函数使用,而非值本身。
与注册生命周期钩子的 API 类似,inject()
必须在组件的 setup()
阶段同步调用。
当使用 TypeScript 时,key 可以是一个类型为 InjectionKey
的 symbol。InjectionKey
是一个 Vue 提供的工具类型,继承自 Symbol
,可以用来同步 provide()
和 inject()
之间值的类型。
示例
假设有一个父组件已经提供了一些值,如前面 provide()
的例子中所示:
<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 注入不含默认值的静态值
const foo = inject('foo')
// 注入响应式的值
const count = inject('count')
// 通过 Symbol 类型的 key 注入
const foo2 = inject(fooSymbol)
// 注入一个值,若为空则使用提供的默认值
const bar = inject('foo', 'default value')
// 注入一个值,若为空则使用提供的函数类型的默认值
const fn = inject('function', () => {})
// 注入一个值,若为空则使用提供的工厂函数
const baz = inject('factory', () => new ExpensiveObject(), true)
</script>
provide和inject也可以传递响应式数据和方法,但是传递响应式数据的时候,官网做了一个推荐使用
当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
如下面这个例子:
<!-- 在供给方组件内 -->
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
官网同样对注入名做了推荐,如果你正在构建大型的应用,包含非常多的依赖提供,或者你正在编写提供给其他开发者使用的组件库,建议最好使用 Symbol 来作为注入名以避免潜在的冲突。可能平常很少用,这里对symbol做一下简单介绍。
symbol
在计算机编程中,Symbol是一种基本数据类型,是在ECMAScript 6 (ES6) 中引入的新特性。它是一种原始数据类型,与数字、字符串、布尔值等类似。
Symbol是一种唯一且不可变的数据类型,每个Symbol值都是唯一的,不会与其他任何值相等,包括其他Symbol值。这使得Symbol非常适合用作对象属性的标识符,以确保不会发生属性名冲突。
创建Symbol可以使用全局Symbol函数,例如:
const mySymbol = Symbol();
也可以传递一个可选的描述字符串作为Symbol的标识,这个描述字符串对于调试和输出Symbol时是可选的,但并不影响Symbol的唯一性,例如:
const mySymbol = Symbol('my unique symbol');
应用到provide和inject中
通常推荐在一个单独的文件中导出这些注入名 Symbol:
// keys.js
export const myInjectionKey = Symbol()
// 在供给方组件中
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'
provide(myInjectionKey, { /*
要提供的数据
*/ });
// 注入方组件
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'
const injected = inject(myInjectionKey)
翻了不少大佬的文章,发现有些理解不同,有说到利用到原型链的,有说没有用到的,仔细理了一下发现是因为vue2和vue3的组件之间的数据绑定和响应式系统不同导致的结果。
在vue2中组件实例方法和属性的继承是通过原型链来实现的,而provide
和 inject
就是基于原型链的属性访问来实现跨组件通信。当一个组件通过 provide
提供数据,它会将这些数据添加到其原型链上,然后子组件通过 inject
可以在原型链上查找并访问这些数据。
在 Vue 3 中 组件的实例方法和属性的继承不再依赖于原型链,而是 引入了 Composition API,它采用了一种不同的方式来组织组件的代码和状态。组件的选项被重构为一个配置对象,其中 setup
函数用于定义组件的响应式数据、计算属性、方法等。这些选项不再依赖于原型链,而是直接导出给组件实例。
这个改进带来了以下好处:
inject
注入的数据类型。因为平常大多数是在Vue3中使用,接下来详细介绍一下在vue3中的原理。
provide
的原理:
provide
是在父组件中使用的选项,用于提供数据给子组件。它实际上是一个函数,它会在父组件实例上创建一个名为 _provided
的对象。_provided
对象存储了提供给子组件的数据,而且这些数据会在整个组件树中可用,子组件可以通过inject
选项来访问这些数据。。inject
的原理:
inject
选项声明需要注入的数据,可以是一个数组、一个对象或一个函数。这些声明告诉Vue 3要从父组件的提供数据中获取哪些属性。inject
注入的数据时,Vue 3会在组件树中向上搜索父组件,直到找到包含提供数据的组件或到达根组件。_provided
属性中获取相应的数据。该方法可以说很方便解决了父组件给多级子组件传值的问题,但是同时也有一定的局限性,下面分析一下优点和缺点
优点:
provide
和 inject
有助于降低组件之间的耦合度。组件不需要直接了解其依赖项的实现细节,而是通过注入来访问这些依赖,这使得组件更加独立和可复用。provide
注入,代码变得更清晰和易于维护。这对于大型应用程序来说特别有价值。provide
和 inject
可以用于共享全局配置、服务或状态管理等全局性的依赖项,使得应用程序更容易扩展和维护。缺点:
provide
和 inject
可能会增加一些不必要的复杂性。这些特性最有价值的地方通常在大型和复杂的应用程序中。provide
和 inject
,将所有东西都注入到组件中,导致不必要的复杂性和混乱。需要谨慎权衡。provide
和 inject
更适合用于共享全局配置和服务等情况,对于局部的、仅在某个组件内部使用的依赖项,使用 props 更合适。总的来说,provide
和 inject
是一种强大的依赖注入机制,特别适用于大型和复杂的应用程序,以提高代码的可维护性、可测试性和可扩展性。但在小型应用中,可能会增加一些复杂性,需要谨慎使用。在使用时,需要根据具体的场景和需求来判断是否使用这些特性,在组件封装过程中,也可以根据情况进行使用。
往期更新
参考资料
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。