当管理父组件和子组件之间的数据时,Vue 使我们能够使用称为属性的东西将数据从父组件传递到子组件。 属性只能沿一个方向流动,从父组件到子组件(以及进一步向下)。当父元素发生状态更改时,Vue 将重新渲染依赖于属性值的组件。
在大多数情况下,使用属性能够达到预期效果。然而,在组件树中包含大量组件的大型应用程序中,属性传递值的方式可能会变得难以维护,因为需要在组件树的每个组件中声明属性。
当考虑如何在大量组件之间管理数据时,通常最好能有一种允许以可维护和可管理的方式管理应用程序级状态的解决方案(例如,创建可重用存储、使用 Pinia 等)。我们在8.状态管理(待更新)一文中更详细地讨论了这一点。
然而,Vue 为了避免在应用程序中进行复杂的属性穿透而提供了一种模式,它称为依赖注入模式。
依赖注入
Vue 中的 provide() 函数允许我们通过组件树传递数据,而无需进行属性穿透(即在每个级别手动向下传递属性)。另一方面, inject() 函数在子组件中使用,以访问其父组件或任何祖先组件提供的数据或方法。
我们将通过一个简单的示例来说明如何做到这一点。假设我们有一个名为 App 的父组件,它想要与其子组件 ChildComponent 共享一段数据。我们可以在父组件中使用 provide() 来使数据可供其所有子组件使用,而不是将此数据作为属性传递。
<template>
<div id="app">
<ChildComponent />
</div>
</template>
<script setup>
import { provide } from "vue";
import ChildComponent from "./components/ChildComponent";
provide("data", "Data from parent!");
</script>
然后,我们可以借助 inject() 函数访问 ChildComponent 中提供的数据。
<template>
<div>
<p>{{ data }}</p>
</div>
</template>
<script setup>
import { inject } from "vue";
const data = inject("data");
</script>
通过在子组件( ChildComponent )中指定 inject("data") ,我们可以直接从父组件访问提供的 data 值。然后我们将 data 绑定到模板以显示其值。
通过依赖注入,即使我们在组件层次树中有许多子组件,我们也会注意到与上面看到的相同的行为。例如,假设我们有 <ChildComponent /> 、 <ChildComponent2 /> 、 <ChildComponent3 /> 、 <ChildComponent4 /> 和 <ChildComponent5 /> 组件,其中每个子组件是另一个组件的父组件。
<!-- ChildComponent5 -->
<template>
<div>
<p>{{ data }}</p>
</div>
</template>
<script setup>
import { inject } from "vue";
const data = inject("data");
</script>
<!-- ------------- -->
<!-- ChildComponent4 -->
<template>
<ChildComponent5 />
</template>
<script setup>
import ChildComponent5 from "./ChildComponent5";
</script>
<!-- ------------- -->
<!-- ChildComponent3 -->
<template>
<ChildComponent4 />
</template>
<script setup>
import ChildComponent4 from "./ChildComponent4";
</script>
<!-- ------------- -->
<!-- ChildComponent2 -->
<template>
<ChildComponent3 />
</template>
<script setup>
import ChildComponent3 from "./ChildComponent3";
</script>
<!-- ------------- -->
<!-- ChildComponent -->
<template>
<ChildComponent2 />
</template>
<script setup>
import ChildComponent2 from "./ChildComponent2";
</script>
<!-- ------------- -->
<!-- App -->
<template>
<div id="app">
<ChildComponent />
</div>
</template>
<script setup>
import { provide } from "vue";
import ChildComponent from "./components/ChildComponent";
provide("data", "Data from parent!");
</script>
<!-- ------------- -->
来自父 <App /> 组件的数据将在 <ChildComponent5 /> 组件中渲染,而无需通过树中的每个组件进行数据穿透,这要归功于依赖注入!
除了能够从父组件获取 provide() 数据之外,我们还可以将 provide() 提升到应用程序级别(即我们实例化 Vue 应用程序的位置)。
import { createApp } from "vue";
import App from "./App.vue";
import "./styles.css";
const app = createApp(App);
// app-level provide
app.provide("data", "Data from parent!");
app.mount("#app");
由于应用程序级别提供了使数据可供所有组件使用的功能,因此它们在创建插件(为整个 Vue 应用程序添加功能的独立代码)时通常很有用。
属性 vs 依赖注入
我们什么时候在属性和依赖注入模式之间进行选择?两种方法都有其优点和缺点。
使用属性:
- 我们遵循一种清晰的模式,将数据从一个级别增量传递到另一个级别(优点)。
- 但是,如果我们的组件层次结构树包含大量组件,则一次一层地传递属性数据的过程可能会变得很麻烦(缺点)。
使用依赖注入:
- 子组件可以直接访问位于上方多个级别的父组件的数据,从而无需在每个级别向下传递数据(优点)。
- 然而,当出现错误时,依赖注入的调试可能会更具挑战性。在具有众多不同 provide 的大型应用程序中,这一挑战变得更加明显(缺点)。
依赖注入模式最适合应用程序范围的客户端数据,例如主题信息、区域设置/语言首选项和用户身份验证详细信息。这些类型的数据可以通过依赖注入得到更好的管理,因为应用程序中的任何组件都可能需要在任何给定时间访问它们。
另一方面,当数据只需要隔离在一组特定的组件中时,属性是理想的选择。