依赖注入
在父组件和子组件之间进行状态管理时,Vue 为我们提供了 props
来由上至下传递数据。Props 只能依照从父组件向子组件这个方向传递,即使子组件再向下传递也是一样。当父组件中的状态改变时,Vue 会重新渲染依赖于这些状态的组件。
大多数情况下 props 都很好用。然而,在一些大型的应用中,通常包含特别多的组件,这些组件构成一个组件树,这时 props 将变得难以维护。因为这时需要在组件树种的每个组件中都声明同样的 props。
当思考如何在大量的组件之间管理数据时,通常最佳的选择是使用应用级的状态,并使用易于维护且易于管理的状态管理工具进行管理。例如使用 Pinia 等工具创建一个可复用的状态库。我们在 状态管理 一章中对此进行了更加详细的讨论。
然而,Vue 同样提供了一种模式,避免在 Vue 应用中出现复杂的 props 透传问题。这种模式叫作依赖注入。
提供和注入
Vue 中的 provide()
函数允许我们在组件树中传递数据,而不需要逐级透传 props(即在每级的组件中都手动传递 props)。另一方面,在后代组件中使用 inject()
方法来访问父组件或者是祖先组件中提供的数据或方法。
我们下面通过一个简答的例子,来看一下依赖注入是如何工作的。假设我们有一个父组件叫作 App
,父组件想要和子组件 ChildComponent
共享一条数据。如果不使用 prop 来传递数据,我们可以在父组件中使用 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>
接下来我们在子组件 ChildComponent
中使用 inject()
来访问这条父组件提供的数据。
<template>
<div>
<p>{{ data }}</p>
</div>
</template>
<script setup>
import { inject } from "vue";
const data = inject("data");
</script>
通过在子组件中使用 inject("data")
,我们直接访问了父组件中提供的 data
。我们在子组件的模板中渲染 data
。
通过提供和注入,即使组件树中有很多层级,例如我们有更多的子组件 <ChildComponent />
、<ChildComponent2 />
、<ChildComponent3 />
、<ChildComponent4 />
、<ChildComponent5 />
,它们之间构成多级的组件树,我们还是会看到和上面例子中相同的结果,因为每个子组件都是另一个子组件的父组件。
<template>
<div>
<p>{{ data }}</p>
</div>
</template>
<script setup>
import { inject } from "vue";
const data = inject("data");
</script>
<template>
<ChildComponent5 />
</template>
<script setup>
import ChildComponent5 from "./ChildComponent5";
</script>
<template>
<ChildComponent4 />
</template>
<script setup>
import ChildComponent4 from "./ChildComponent4";
</script>
<template>
<ChildComponent3 />
</template>
<script setup>
import ChildComponent3 from "./ChildComponent3";
</script>
<template>
<ChildComponent2 />
</template>
<script setup>
import ChildComponent2 from "./ChildComponent2";
</script>
<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 />
中被渲染,而这其中不需要依次在每一层组件中透传数据。让我们说谢谢提供和注入!
译注
原文就是 thanks to provide/inject!
,我没说谢谢 Cheems 不错了。
除了由父组件提供数据之外,我们还可以将 provide()
提升到应用级别,也就是初始化 Vue 实例的位置。
import { createApp } from "vue";
import App from "./App.vue";
import "./styles.css";
const app = createApp(App);
// 应用级 provide
app.provide("data", "Data from parent!");
app.mount("#app");
应用级的 provide
在为所有组件提供数据时很好用,这个特性更多用来在创建 插件 时使用。插件通常是一段独立的逻辑,用于为整个应用添加独立的功能。
Props 和 依赖注入
什么时候该用 props,什么时候又该用依赖注入模式呢?它们二者都各有优缺点。
Props
优点:使用一种很清晰的传值方式,将数据传递给下级的组件。
缺点:如果组件树很复杂,逐层透传 props 会特别麻烦。
依赖注入
优点:子组件可以直接访问祖先组件提供的数据,无需逐级透传。
缺点:当程序出现 bug 时,依赖注入引起的问题更不容易调试。当大型应用中包含多个 provide 时,这个缺陷会更加明显。
依赖注入模式是在应用层面保存数据的最佳选择,例如主题、本地化语言设置、用户认证信息等。这些种类的数据可以通过依赖注入进行更好的管理,因为应用中的任何组件都可能在任何时间需要对它们进行访问。
另一方面,当数据只需要在一组特定的组件中使用时,props 此时是最佳选择。