在 Pinia 的 store 中存在很多基础 API,比如:获取 store id $id、增加 action 调用回调 $onAction()、重置 store $reset()、变更 store $patch()、订阅 $subscribe()、移除 store $dispose、获取所有 state $state 等。我们逐个分析。
基础的 API 首先被储存在 partialStore 中,然后创建一个 store 常量,并且把这些基础 API 和 store 的内容都合并到 store 常量中。
/** * 具有 state 和 功能 的基本 store,但不能直接使用。 */const partialStore = { _p: pinia, $id, $onAction, $patch, $reset, $subscribe, $dispose,} as _StoreWithState<Id, S, G, A>
(4) Store 和 基础 API 合并
在 (2) 和 (3) 中我们创建了 store 的基本内容和基础的API,现在新建一个变量,并把它们合并到一块:
/** * 创建一个响应式的 store 对象 * 将基础函数合并到 store 中 */const store: Store<Id, S, G, A> = reactive( __DEV__ || USE_DEVTOOLS ? assign( { _hmrPayload, _customProperties: markRaw(new Set<string>()), // devtools custom properties }, partialStore // must be added later // setupStore ) : partialStore) as unknown as Store<Id, S, G, A>assign(toRaw(store), setupStore)
现在,还缺少一个获取所有 state 得属性: $state ,我们使用 defineProperty 给 store 增加 $state 属性 :
// 使用它而不是 computed with setter 可以在任何地方创建它,而无需将计算的生命周期链接到首次创建 store 的任何地方。// 给 store 定义 $state 属性,方便获取全部的 stateObject.defineProperty(store, '$state', { get: () => (__DEV__ && hot ? hotState.value : pinia.state.value[$id]), set: (state) => { /* istanbul ignore if */ if (__DEV__ && hot) { throw new Error('cannot set hotState') } $patch(($state) => { assign($state, state) }) },})
/** * 创建 选项式 store * @param id Store ID * @param options 配置选项 * @param pinia Pinia 实例 * @param hot 热更新相关 * @returns 创建的 store */function createOptionsStore< Id extends string, S extends StateTree, G extends _GettersTree<S>, A extends _ActionsTree,>( id: Id, options: DefineStoreOptions<Id, S, G, A>, pinia: Pinia, hot?: boolean): Store<Id, S, G, A> { Log('createOptionsStore()') const { state, actions, getters } = options const initialState: StateTree | undefined = pinia.state.value[id] let store: Store<Id, S, G, A> /** * 自定义一个 setup 函数 * @returns store */ function setup() { if (!initialState && (!__DEV__ || !hot)) { /* istanbul ignore if */ if (isVue2) { set(pinia.state.value, id, state ? state() : {}) } else { pinia.state.value[id] = state ? state() : {} } } // 避免在 pinia.state.value 中创建 state const localState = __DEV__ && hot ? // 使用 ref() 解包状态中的引用 toRefs(ref(state ? state() : {}).value) : toRefs(pinia.state.value[id]) return assign( localState, actions, Object.keys(getters || {}).reduce( (computedGetters, name) => { if (__DEV__ && name in localState) { // getter 不能和 state 属性同名 console.warn( `[🍍]: A getter cannot have the same name as another state property. Rename one of them. Found with "${name}" in store "${id}".` ) } // 把 getter 转为 computed computedGetters[name] = markRaw( computed(() => { setActivePinia(pinia) // it was created just before const store = pinia._s.get(id)! // allow cross using stores /* istanbul ignore next */ if (isVue2 && !store._r) return // @ts-expect-error // return getters![name].call(context, context) // TODO: avoid reading the getter while assigning with a global variable return getters![name].call(store, store) }) ) return computedGetters }, {} as Record<string, ComputedRef> ) ) } store = createSetupStore(id, setup, options, pinia, hot, true) return store as any}
通过生成一个对象,传递到组件的 computed 字段 以允许在不使用组合式 API(setup()) 的情况下使用 store。 它接受一个 store 定义的列表参数。
/** * 通过生成一个对象,传递到组件的 computed 字段 以允许在不使用组合式 API(setup())的情况下使用 store。 它接受一个 store 定义的列表参数。 * * @example * ```js showLineNumbers * export default { * computed: { * // other computed properties * ...mapStores(useUserStore, useCartStore) * }, * * created() { * this.userStore // store with id "user" * this.cartStore // store with id "cart" * } * } * ``` * * @param stores - 要映射到 object 的 stores 列表 */export function mapStores<Stores extends any[]>( // 所有参数放入 stores 数组,所以 store 不需要在包裹一层数组 ...stores: [...Stores]): _Spread<Stores> { // 直接将 store 通过参数传递即可,不需要放到数组中,如果放到了数组中就抛出警告 if (__DEV__ && Array.isArray(stores[0])) { console.warn( `[🍍]: Directly pass all stores to "mapStores()" without putting them in an array:\n` + `Replace\n` + `\tmapStores([useAuthStore, useCartStore])\n` + `with\n` + `\tmapStores(useAuthStore, useCartStore)\n` + `This will fail in production if not fixed.` ) stores = stores[0] } // 遍历所有传进来的 useStore 并执行,然后 return 出去就得到了所有的 store return stores.reduce((reduced, useStore) => { // $id 是 defineStore 添加的 // @ts-expect-error: $id is added by defineStore reduced[useStore.$id + mapStoreSuffix] = function (this: ComponentPublicInstance) { return useStore(this.$pinia) } return reduced }, {} as _Spread<Stores>)}
3. mapState
通过生成一个对象,并传递至组件的 computed 字段, 以允许在不使用组合式 API(setup())的情况下使用一个 store 的 state 和 getter。 该对象的值是 state 属性/getter, 而键是生成的计算属性名称。 你也可以选择传递一个自定义函数,该函数将接收 store 作为其第一个参数。 注意,虽然它可以通过 this 访问组件实例,但它没有标注类型。
/** * 通过生成一个对象,并传递至组件的 computed 字段, 以允许在不使用组合式 API(setup())的情况下使用一个 store 的 state 和 getter。 该对象的值是 state 属性/getter, 而键是生成的计算属性名称。 你也可以选择传递一个自定义函数,该函数将接收 store 作为其第一个参数。 注意,虽然它可以通过 this 访问组件实例,但它没有标注类型。 * * @example * ```js showLineNumbers * export default { * computed: { * // other computed properties * // useCounterStore has a state property named `count` and a getter `double` * ...mapState(useCounterStore, { * n: 'count', * triple: store => store.n * 3, * // note we can't use an arrow function if we want to use `this` * custom(store) { * return this.someComponentValue + store.n * }, * doubleN: 'double' * }) * }, * * created() { * this.n // 2 * this.doubleN // 4 * } * } * ``` * * @param useStore - defineStore 中返回的 useStore * @param keyMapper - state 的属性名 或 getters 的对象 */export function mapState< Id extends string, S extends StateTree, G extends _GettersTree<S>, A, KeyMapper extends Record<string, keyof S | keyof G | ((store: Store<Id, S, G, A>) => any)>,>( useStore: StoreDefinition<Id, S, G, A>, keyMapper: KeyMapper): _MapStateObjectReturn<Id, S, G, A, KeyMapper>/** * Allows using state and getters from one store without using the composition * API (`setup()`) by generating an object to be spread in the `computed` field * of a component. * * @example * ```js showLineNumbers * export default { * computed: { * // other computed properties * ...mapState(useCounterStore, ['count', 'double']) * }, * * created() { * this.count // 2 * this.double // 4 * } * } * ``` * * @param useStore - defineStore 中返回的 useStore * @param keys - state 的属性名 或 getters 的数组 */export function mapState< Id extends string, S extends StateTree, G extends _GettersTree<S>, A, Keys extends keyof S | keyof G,>( useStore: StoreDefinition<Id, S, G, A>, // key数组,内容仅限于 State 和 Getter 的 key keys: readonly Keys[]): _MapStateReturn<S, G, Keys>/** * Allows using state and getters from one store without using the composition * API (`setup()`) by generating an object to be spread in the `computed` field * of a component. * * @param useStore - defineStore 中返回的 useStore * @param keysOrMapper - array or object */export function mapState<Id extends string, S extends StateTree, G extends _GettersTree<S>, A>( useStore: StoreDefinition<Id, S, G, A>, keysOrMapper: any): _MapStateReturn<S, G> | _MapStateObjectReturn<Id, S, G, A> { // 此处逻辑和 mapAction 很像 return Array.isArray(keysOrMapper) ? keysOrMapper.reduce( (reduced, key) => { reduced[key] = function (this: ComponentPublicInstance) { // 和 mapAction 的区别:mapAction 取出的是经过 wrapAction 的 action ,然后在这调用了一下 return useStore(this.$pinia)[key] } as () => any return reduced }, {} as _MapStateReturn<S, G> ) : Object.keys(keysOrMapper).reduce( (reduced, key: string) => { // @ts-expect-error reduced[key] = function (this: ComponentPublicInstance) { const store = useStore(this.$pinia) const storeKey = keysOrMapper[key] // 由于某种原因,TS 无法将 storeKey 的类型推断为函数 return typeof storeKey === 'function' ? (storeKey as (store: Store<Id, S, G, A>) => any).call(this, store) : store[storeKey] } return reduced }, {} as _MapStateObjectReturn<Id, S, G, A> )}
4. mapGetters
mapGetters 已废弃,直接使用 mapState 即可。
/** * Alias for `mapState()`. You should use `mapState()` instead. * @deprecated use `mapState()` instead. */export const mapGetters = mapState
5. mapWritableState
在使用 $mapState 把 state 导入 computed 时,如果直接去修改 state 的值是不允许的。
$mapWritableState****除了创建的计算属性的 setter,其他与 mapState() 相同, 所以 state 可以被修改。 与 mapState() 不同的是,只有 state 属性可以被添加。
/** * 除了创建的计算属性的 setter,其他与 mapState() 相同, 所以 state 可以被修改。 与 mapState() 不同的是,只有 state 属性可以被添加。 * * @param useStore - store to map from * @param keyMapper - object of state properties */export function mapWritableState< Id extends string, S extends StateTree, G extends _GettersTree<S>, A, KeyMapper extends Record<string, keyof S>,>( useStore: StoreDefinition<Id, S, G, A>, keyMapper: KeyMapper): _MapWritableStateObjectReturn<S, KeyMapper>/** * Allows using state and getters from one store without using the composition * API (`setup()`) by generating an object to be spread in the `computed` field * of a component. * * @param useStore - store to map from * @param keys - array of state properties */export function mapWritableState< Id extends string, S extends StateTree, G extends _GettersTree<S>, A, Keys extends keyof S,>( useStore: StoreDefinition<Id, S, G, A>, keys: readonly Keys[]): { [K in Keys]: { get: () => S[K] set: (value: S[K]) => any }}/** * Allows using state and getters from one store without using the composition * API (`setup()`) by generating an object to be spread in the `computed` field * of a component. * * @param useStore - store to map from * @param keysOrMapper - array or object */export function mapWritableState< Id extends string, S extends StateTree, G extends _GettersTree<S>, A, KeyMapper extends Record<string, keyof S>,>( useStore: StoreDefinition<Id, S, G, A>, keysOrMapper: Array<keyof S> | KeyMapper): _MapWritableStateReturn<S> | _MapWritableStateObjectReturn<S, KeyMapper> { // 也是对于数组和对象的分别处理 // 返回包含 get 和 set 函数的对象,交给 computed 处理 return Array.isArray(keysOrMapper) ? keysOrMapper.reduce((reduced, key) => { // @ts-ignore reduced[key] = { get(this: ComponentPublicInstance) { return useStore(this.$pinia)[key] }, set(this: ComponentPublicInstance, value) { // it's easier to type it here as any return (useStore(this.$pinia)[key] = value as any) }, } return reduced }, {} as _MapWritableStateReturn<S>) : Object.keys(keysOrMapper).reduce( (reduced, key: keyof KeyMapper) => { // @ts-ignore reduced[key] = { get(this: ComponentPublicInstance) { return useStore(this.$pinia)[keysOrMapper[key]] }, set(this: ComponentPublicInstance, value) { // it's easier to type it here as any return (useStore(this.$pinia)[keysOrMapper[key]] = value as any) }, } console.log(reduced) return reduced }, {} as _MapWritableStateObjectReturn<S, KeyMapper> )}