provide/inject 实现祖→后代跨层级状态共享,需祖先顶层同步 provide 响应式数据与方法,后代顶层 inject 并谨慎解构以保持响应性,适用于主题、表单等局部场景,不替代 Pinia/Vuex。

在 setup 中用 provide 和 inject 实现跨层级状态共享,核心是祖先组件主动提供响应式数据或逻辑,后代组件按需注入使用。它不依赖 props 透传,也不需要状态管理库,适合主题、表单上下文、配置等局部但跨多层的场景。
祖先组件中正确 provide
必须在 setup 函数顶层同步调用 provide,不能放在 if、setTimeout 或 Promise 回调里:
- 推荐用 Symbol 作 key(如
const themeKey = Symbol('theme')),避免字符串冲突 - 值要是响应式的:用
ref或reactive包裹;直接传普通对象,后代拿到的是静态快照,改了不更新 - 可同时提供数据和方法,例如:
const color = ref('blue')<br>provide(themeKey, { color, setColor: (c) => color.value = c })
后代组件中安全 inject
同样在 setup 顶层调用 inject,注意响应性和健壮性:
- key 必须与 provide 时完全一致(类型和值都相同),Symbol 比字符串更可靠
- 如果注入的是 ref,不能解构使用(如
const { color } = inject(themeKey)会丢失响应性),应保持theme.color.value或用toRef提取 - 建议始终设默认值:
inject(themeKey, { color: ref('light') }),或用工厂函数初始化复杂默认值
常见搭配:数据 + 方法一起注入
不只是传值,还能把修改逻辑一并暴露,实现“受控但解耦”:
- 祖先提供:
const count = ref(0)<br>provide('counter', { count, increment: () => count.value++ }) - 后代注入:
const { count, increment } = inject('counter')
注意:这里能解构,因为counter是对象,不是 ref - 模板中直接用:
<button @click="increment">+1</button><p>{{ count }}</p>
适用边界要清楚
provide/inject 是轻量级依赖传递机制,不是万能方案:
- 只支持祖→后代单向通信,兄弟组件之间无法互通
- 不替代 Pinia/Vuex —— 它不管理状态生命周期、不支持调试工具、无模块划分能力
- 注入值本身不自动响应,但注入的 ref 或 reactive 属性是响应的;修改仍需通过响应式方式触发更新


















