createInjectionState
创建可以注入到组件中的全局状态。
Demo
<script setup lang="ts">
import ButtonComponent from './demo/ButtonComponent.vue'
import CountComponent from './demo/CountComponent.vue'
import RootComponent from './demo/RootComponent.vue'
</script>
<template>
<div>
<RootComponent>
<ButtonComponent />
<CountComponent />
</RootComponent>
</div>
</template>
使用
useCounterStore.ts
import { createInjectionState } from '@vueuse/core'
// useCounterStore.ts
import { computed, shallowRef } from 'vue'
// 创建一个可注入的状态,返回 useProvideCounterStore 和 useCounterStore 两个 Hook
const [useProvideCounterStore, useCounterStore] = createInjectionState((initialValue: number) => {
// state 定义状态
const count = shallowRef(initialValue)
// getters 定义计算属性:count 的两倍
const double = computed(() => count.value * 2)
// actions 定义操作:增加计数器
function increment() {
count.value++
}
// 返回状态、计算属性和操作
return { count, double, increment }
})
// 导出 useProvideCounterStore,用于在父组件中提供状态
export { useProvideCounterStore }
// If you want to hide `useCounterStore` and wrap it in default value logic or throw error logic, please don't export `useCounterStore`
// 如果你想隐藏 useCounterStore 并使用默认值或抛出错误逻辑,请不要导出 useCounterStore
export { useCounterStore }
// 提供一个带有默认值的 Hook
export function useCounterStoreWithDefaultValue() {
return useCounterStore() ?? {
count: shallowRef(0),
double: shallowRef(0),
increment: () => {},
}
}
// 提供一个如果未提供状态则抛出错误的 Hook
export function useCounterStoreOrThrow() {
const counterStore = useCounterStore()
if (counterStore == null)
throw new Error('Please call `useProvideCounterStore` on the appropriate parent component')
return counterStore
}
RootComponent.vue
<!-- RootComponent.vue -->
<script setup lang="ts">
import { useProvideCounterStore } from './useCounterStore'
// 调用 useProvideCounterStore 并传入初始值 0
// 所有该组件的后代组件都可以通过 useCounterStore() 获取到这个状态,实现跨层级组件通信,无需逐层传递 props。
useProvideCounterStore(0) // 状态提供者
</script>
<template>
<div>
<slot />
</div>
</template>
具体作用如下:
- 状态提供者(
Provider):该组件使用useProvideCounterStore(0) 在当前组件树中提供一个初始值为 0 的计数器状态。 这个状态是通过createInjectionState创建的,可以被其子组件通过useCounterStore()注入使用。 - 使用场景:所有该组件的后代组件都可以通过
useCounterStore()获取到这个状态,实现跨层级组件通信,无需逐层传递props。 - 根组件结构:
<slot />表示这是一个布局组件,通常用于包裹其他组件。它本身不展示内容,而是作为状态提供者,为子组件提供共享状态。 - 依赖注入(
Injection):使用createInjectionState创建的状态是通过 Vue 的依赖注入机制实现的,因此子组件无需显式传递即可访问该状态。
总结:这个组件的作用是在组件树顶层提供一个可注入的计数器状态(初始值为 0),供其子组件使用,实现跨层级状态共享。
CountComponent.vue
<!-- CountComponent.vue -->
<script setup lang="ts">
import { useCounterStore } from './useCounterStore'
// use non-null assertion operator to ignore the case that store is not provided.
const { count, double } = useCounterStore()!
// if you want to allow component to working without providing store, you can use follow code instead:
// const { count, double } = useCounterStore() ?? { count: shallowRef(0), double: shallowRef(0) }
// also, you can use another hook to provide default value
// const { count, double } = useCounterStoreWithDefaultValue()
// or throw error
// const { count, double } = useCounterStoreOrThrow()
</script>
<template>
<ul>
<li>
count: {{ count }}
</li>
<li>
double: {{ double }}
</li>
</ul>
</template>
- 调用
useCounterStore()获取注入的状态对象。 - 使用非空断言操作符 ! 表示开发者确信状态一定存在,避免 TypeScript 报错。
- 从状态对象中解构出
count和double,分别表示当前计数值和其两倍值。
⚠️ 注意:如果状态未被正确提供,使用 ! 会导致运行时错误。因此,文档中提供了几种替代方案。
??默认值处理:如果状态未提供,则使用默认值{ count: shallowRef(0), double: shallowRef(0) }。useCounterStoreWithDefaultValue():封装了默认值逻辑,更清晰。useCounterStoreOrThrow():如果状态未提供则抛出错误,用于确保状态必须被提供。
ButtonComponent.vue
<!-- ButtonComponent.vue -->
<script setup lang="ts">
import { useCounterStore } from './useCounterStore'
// use non-null assertion operator to ignore the case that store is not provided.
const { increment } = useCounterStore()!
</script>
<template>
<button @click="increment">
+
</button>
</template>
提供一个自定的 InjectionKey
useCounterStore.ts
import { createInjectionState } from '@vueuse/core'
// useCounterStore.ts
import { computed, shallowRef } from 'vue'
// custom injectionKey
const CounterStoreKey = 'counter-store'
const [useProvideCounterStore, useCounterStore] = createInjectionState((initialValue: number) => {
// state
const count = shallowRef(initialValue)
// getters
const double = computed(() => count.value * 2)
// actions
function increment() {
count.value++
}
return { count, double, increment }
}, { injectionKey: CounterStoreKey })
为什么需要自定义 injectionKey?
- 避免命名冲突:当多个状态被注入到同一个组件树中时,使用默认的自动
key可能导致冲突。 - 可读性更强:使用有意义的
key名称(如 'counter-store')有助于调试和理解状态的用途。 - 与 Vue 原生
InjectionKey兼容:你也可以使用InjectionKey<T>类型来获得更好的类型推导支持。
提供一个自定义的默认值
useCounterStore.ts
import { createInjectionState } from '@vueuse/core'
// useCounterStore.ts
import { computed, shallowRef } from 'vue'
const [useProvideCounterStore, useCounterStore] = createInjectionState((initialValue: number) => {
// state
const count = shallowRef(initialValue)
// getters
const double = computed(() => count.value * 2)
// actions
function increment() {
count.value++
}
return { count, double, increment }
}, { defaultValue: 0 }) // 通过 defaultValue: 0 为状态提供一个默认值,使得在未找到提供者的情况下也能安全地使用默认状态。
类型声明
export interface CreateInjectionStateOptions<Return> {
/**
* 自定义的注入 key(可选)
*/
injectionKey?: string | InjectionKey<Return>
/**
* 如果未 provide 也未注入成功,则使用这个默认值
*/
defaultValue?: Return
}
/**
* 创建可以注入到组件中的全局状态。
*
* @see https://vueuse.org/createInjectionState
*
*/
export declare function createInjectionState<
Arguments extends Array<any>, // 传入组合式函数的参数
Return, // 组合式函数返回的状态类型
>(
composable: (...args: Arguments) => Return, // 你定义的状态函数
options?: CreateInjectionStateOptions<Return>, // 可选配置
): readonly [
// 用于组件树中较上层组件(如页面或 layout)中调用,将状态通过 provide 提供给子组件。
useProvidingState: (...args: Arguments) => Return,
// 用于下层组件中 inject 注入状态。
useInjectedState: () => Return | undefined,
]
createInjectionState 是 VueUse 提供的一个高级工具函数,用于在组件树中创建 可注入的全局状态。它结合了 Vue 的依赖注入机制(provide/inject)和组合式函数(composables),可以让你定义一份状态并在多个组件中共享而不需要手动传 props 或管理全局变量。