用provide和inject实现数据传递

使用props将父组件的数据传递到含有多层结构的子组件中是非常麻烦的,若是想要在子组件中改变这个数据,再用emit一层层往上触发事件的过程可以说是“灾难性”的。

provideinject则非常友好的处理了这个问题,首先需要明确的是,provide/inject并非是响应式的,本身是自上而下的数据传递。Vue3起在provide/inject中可以组合使用新增的响应式API => refopen in new window来达到数据响应式的能力。

预设一个场景:页面包含导航栏和侧边栏,点击导航栏某个按钮可以控制侧边栏的显示和隐藏。涉及到的组件包含<App /><Nav /><Aside />

实现过程

通过<router-view>渲染页面的根组件<App />向后代组件提供(provide)响应式数据支持,<Nav />组件中获取(inject)并调用切换<Aside />显示的方法,<Aside />组件中获取(inject)并使用数据(v-show/v-if)控制侧边栏的显示。

简单案例

<!-- App.vue -->
<template>
  <router-view />
</template>

<script setup lang="ts">
import { ref, provide } from 'vue'

// 通过 ref 创建响应式数据
const asideVisible = ref(false);
// 提供切换显示的函数
const toggleAsideVisible = () => {
  asideVisible.value = !asideVisible.value
}
provide('asideVisible', asideVisible);
provide("toggleAsideVisible", toggleAsideVisible);
</script>
<!-- Nav.vue -->
<template>
  <div class="nav">
    <div class="logo" @click="toggleAsideVisible">LOGO</div>
  </div>
</template>

<script setup lang="ts">
import { inject } from 'vue';

const toggleAsideVisible = inject<() => void>("toggleAsideVisible");
</script>
<!-- Aside.vue -->
<template>
  <!-- ref 数据在模板使用时会自动解包 -->
  <aside v-show="asideVisible">
    aside
  </aside>
</template>

<script setup lang="ts">
import Nav from '@/components/Nav.vue';
import { inject, Ref } from 'vue';

const asideVisible = inject<Ref<boolean>>("asideVisible");
</script>

注:

  1. ref数据在模板中使用会自动解包,即不用再特别写asideVisible.value
  2. 由于ref创建的数据是响应式的,所以我们可以不在根组件<App />中提供切换方法,转而在特定需要操作ref变量的组件中改变这个值。比如上述示例中可以在<Nav />中创建toggleAsideVisible函数来改变asideVisible的值。但是,这并非是官方所建议的办法(Vue:建议尽可能将对响应式 property 的所有修改限制在定义 provide 的组件内部)。我也认同官方的说法,毕竟谁提供谁处理的方式更易维护!
  3. 将不想被后代组件操作的变量用readonly修饰处理,主要与2中的思想进行结合;
  4. 使用typescript,需明确inject时数据类型;

参考资料:Provide/Inject介绍open in new windowProvide和Inject的响应性open in new windowRef 解包open in new window

最近更新:
Contributors: untilthecore