六维教程

Vue3 依赖注入

如果有个配置要传 5 层组件,用 props 就得”每层接一次、每层传一次”,中间层的组件明明用不到它,也要写声明——这种层层中转叫 props 钻孔(prop drilling),又繁琐又易错。

provide / inject 就是为了解决这个问题,祖先组件提供数据,任意深度的后代组件直接取用,中间的层完全不用管。

基本用法

祖先组件用 provide 提供,后代用 inject 注入:

<!-- 祖先组件 App.vue -->
<script setup>
import { provide, ref } from 'vue'

const themeColor = ref('#f60')

provide('theme', themeColor)          // key 提供出去
</script>
<!-- 任意深度的后代组件 NavBar.vue -->
<script setup>
import { inject } from 'vue'

const theme = inject('theme')          // 按 key 取用
</script>

<template>
  <div :style="{ color: theme }">导航栏</div>
</template>

主题色从 App 一路用到底层 NavBar,中间就算隔着 Header、SideBar 这些组件,它们也完全不用动,这就是”跨级传参”。

provide/inject 和 props 怎么分工

  • props:显式、就近、单向,适合”相邻两层、量少清晰”的传参。
  • provide/inject:跨层、隐式、全局观,适合”主题、当前用户、语言包、全局配置”这类层层都想要的数据。

一般两层以内 props 就够;三层以上且中间层不关心时,考虑 provide/inject。

不要为了小事动用 inject,它让数据来源难查。

也可以用后面讲的 Pinia(全局状态管理,见 Vue3 状态管理入门)做类似的事,看哪种更适合。

注入默认值

inject('key') 在找不到提供者时返回 undefined。可以给默认值,找不到就用默认:

const theme = inject('theme', '#333')

需要更稳的”无值兜底 + 类型提示”,也可以写成 getter 形式,这里了解 inject('key', 默认值) 就够。

读写分离

provideref / reactive,后代拿到的就是响应式数据,祖先一改、后代跟着更新:

<script setup>
import { provide, ref } from 'vue'

const count = ref(0)
const increment = () => count.value++

// 注意后面把修改函数也一起提供
provide('counter', { count, increment })
</script>

后代组件不要直接改注入的值(和 props 同样的单向流动原则)。

正确的做法是:把”修改函数”也 provide 出来,后代只负责调用:

<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'

// 读数据
const { count } = inject('counter')
// 通过提供的函数修改,而不是自己动 count 所在对象
const { increment } = inject('counter')
</script>

每个后代都直接改注入的对象,改的人一多,数据被谁动过、来源在哪都难查。

遵守上面的约定,只通过提供的函数来改,就能绕开这类问题。

常用 key

字符串 key 简单直接,但大项目里容易撞名,可以改用 Symbol() 保证唯一:

// inject-key.js
export const THEME_KEY = Symbol('theme')
provide(THEME_KEY, themeColor)
inject(THEME_KEY)

相同 key 在多层都 provide 了,后代取到谁?

就近原则:取最近的祖先提供的那份。不同分支的后代各取各自分支的值,互不影响。

应用启动就要用的全局数据怎么办?

app.provide('key', value) 可以在创建应用时给”所有组件”提供(一般写在 main.js 里)。适合全局主题、全局请求实例等场景,实战项目里也常见。

速查卡片

场景 写法
提供数据 provide('key', value)
注入数据 const x = inject('key')
注入默认值 inject('key', 默认值)
保持响应式 provide 传 ref / reactive
防止修改 提供修改函数,后代只调用
防撞名 Symbol() 作 key
上一篇
Vue3 作用域插槽
下一篇
Vue3 动态组件与缓存