Lzh on GitHub

创建可以注入到组件中的全局状态。

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 报错。
  • 从状态对象中解构出 countdouble,分别表示当前计数值和其两倍值。

⚠️ 注意:如果状态未被正确提供,使用 ! 会导致运行时错误。因此,文档中提供了几种替代方案。

  • ?? 默认值处理:如果状态未提供,则使用默认值 { 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 或管理全局变量。