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', 默认值) 就够。
读写分离
provide 传 ref / 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 |