Provide/Inject
Provide 的问题是无法追踪数据的来源,在任意层级都能访问导致数据追踪比较困难,不知道是哪一个层级声明了这个或者不知道哪一层级或若干个层级使用了。
介绍
createInjectionState:创建可以注入组件的全局状态。
//useCounterStore.ts
const [useProvideCounterStore, useCounterStore] = createInjectionState(
(initialValue: number => {
// state
const count = ref(initialValue
// getters
const double = computed(( => count.value * 2
// actions
function increment( {
count.value++
}
return { count, double, increment }
}
export { useProvideCounterStore }
// If you want to hide `useCounterStore` and wrap it in default value logic or throw error logic, please don't export `useCounterStore`
export { useCounterStore }
<!-- RootComponent.vue -->
<script setup lang="ts">
import { useProvideCounterStore } from './useCounterStore'
useProvideCounterStore(0
</script>
<template>
<div>
<slot />
</div>
</template>
<!-- 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: ref(0, double: ref(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>
源码
/**
* Create global state that can be injected into components.
*
* @see https://vueuse.org/createInjectionState
*
*/
export function createInjectionState<Arguments extends Array<any>, Return>(
composable: (...args: Arguments => Return,
: readonly [useProvidingState: (...args: Arguments => Return, useInjectedState: ( => Return | undefined] {
const key: string | InjectionKey<Return> = Symbol('InjectionState'
const useProvidingState = (...args: Arguments => {
const state = composable(...args
provide(key, state
return state
}
const useInjectedState = ( => inject(key
return [useProvidingState, useInjectedState]
}
思考
为什么返回的是数组
createInjectionState 返回的数组,使用 demo 中采用的数组解构的方式。那么数组解构和对象解构有什么区别么?
const [count,setCount] =useState(0
之所以用数组解构是因为在调用多个 useState 的时候,方便命名变量。
const [count,setCount] =useState(0
const [double, setDouble] = useState(0;
如果用对象解构,代码会是
const {state:count,setState:setCount} =useState(0
const {state:double, setState:setDouble} = useState(0;
相比之下数组显得代码更加简洁。
const [,setCount] =useState(0
因此数组解构时适合使用所有返回值,并且多次调用方法的情况;对象解构适合只使用其中部分返回值,并且一次调用方法的情况。
const [,useCounterStore] =useCounterStore(
使用例子中的 state 结构
使用案例中将 provide 中的对象分为 state、getters、actions。结构很想 vuex,而 useProvideCounterStore 相当于 vuex 中的 mutation。采用这种结构是因为 provide 的缺点:无法追踪数据的来源,在任意层级都能访问导致数据追踪比较困难,不知道是哪一个层级声明了这个或者不知道哪一层级或若干个层级使用了。
// state
const count = ref(initialValue
// getters
const double = computed(( => count.value * 2
// actions
function increment( {
count.value++
}
readonly
createInjectionState 返回的数组是 readonly 修饰的,useInjectedState 返回的对象并没有用 readonly 修饰,provide/inject 的缺点就是状态对象不好跟踪,容易导致状态变更失控。既然提供了 useProvidingState 修改状态的方法,useInjectedState 返回的状态如果是只读的能更好防止状态变更失控。