From: Eduardo San Martin Morote Date: Fri, 27 Aug 2021 13:16:57 +0000 (+0200) Subject: feat: allow stores to be cross used X-Git-Tag: @pinia/nuxt@0.0.2~26 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=cda365875c599e9786ab3479d42e8e6b3bb0fc23;p=thirdparty%2Fvuejs%2Fpinia.git feat: allow stores to be cross used --- diff --git a/packages/pinia/__tests__/combinedStores.spec.ts b/packages/pinia/__tests__/combinedStores.spec.ts new file mode 100644 index 00000000..81754fa7 --- /dev/null +++ b/packages/pinia/__tests__/combinedStores.spec.ts @@ -0,0 +1,72 @@ +import { computed, ref } from 'vue' +import { createPinia, defineStore, setActivePinia } from '../src' + +describe('Composing stores', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + function useCounter() { + const n = ref(0) + const double = computed(() => n.value) + + function increment(amount = 1) { + n.value += amount + } + + return { n, double, increment } + } + + const useStoreA = defineStore('a', () => { + const { n, double, increment } = useCounter() + const b = useStoreB() + + function changeB() { + b.n++ + } + + function getInnerB() { + return b + } + + return { n, increment, double, changeB, getInnerB } + }) + + const useStoreB = defineStore('b', () => { + const { n, double, increment } = useCounter() + const a = useStoreA() + + function changeA() { + a.n++ + } + + function getInnerA() { + return a + } + + return { n, increment, double, changeA, getInnerA } + }) + + it('works', () => { + expect(() => { + useStoreA() + useStoreB() + }).not.toThrow() + }) + + it('they can use each other', () => { + const b = useStoreB() + const a = useStoreA() + + expect(a).toBe(b.getInnerA()) + expect(b).toBe(a.getInnerB()) + + expect(a.n).toBe(0) + b.changeA() + expect(a.n).toBe(1) + + expect(b.n).toBe(0) + a.changeB() + expect(b.n).toBe(1) + }) +}) diff --git a/packages/pinia/src/store.ts b/packages/pinia/src/store.ts index ed3fbdcb..d77f4daf 100644 --- a/packages/pinia/src/store.ts +++ b/packages/pinia/src/store.ts @@ -213,17 +213,6 @@ function createSetupStore< const hotState = ref({} as S) - /* istanbul ignore if */ - if (__DEV__ && !pinia._e.active) { - throw new Error('Pinia destroyed') - } - - // TODO: idea create skipSerialize that marks properties as non serializable and they are skipped - const setupStore = pinia._e.run(() => { - scope = effectScope() - return scope.run(() => setup()) - })! - function $patch(stateMutation: (state: UnwrapRef) => void): void function $patch(partialState: DeepPartial>): void function $patch( @@ -346,65 +335,6 @@ function createSetupStore< hotState, }) - // overwrite existing actions to support $onAction - for (const key in setupStore) { - const prop = setupStore[key] - - if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) { - // mark it as a piece of state to be serialized - if (__DEV__ && hot) { - set(hotState.value, key, toRef(setupStore as any, key)) - // createOptionStore directly sets the state in pinia.state.value so we - // can just skip that - } else if (!buildState) { - if (isVue2) { - set(pinia.state.value[$id], key, prop) - } else { - pinia.state.value[$id][key] = prop - } - // TODO: avoid if state exists for SSR - } - - if (__DEV__) { - _hmrPayload.state.push(key) - } - // action - } else if (typeof prop === 'function') { - // @ts-expect-error: we are overriding the function we avoid wrapping if - const actionValue = __DEV__ && hot ? prop : wrapAction(key, prop) - // this a hot module replacement store because the hotUpdate method needs - // to do it with the right context - if (isVue2) { - set(setupStore, key, actionValue) - } else { - // @ts-expect-error - setupStore[key] = actionValue - } - - if (__DEV__) { - _hmrPayload.actions[key] = prop - } - - // list actions so they can be used in plugins - // @ts-expect-error - optionsForPlugin.actions[key] = prop - } else if (__DEV__) { - // add getters for devtools - if (isComputed(prop)) { - _hmrPayload.getters[key] = buildState - ? // @ts-expect-error - options.getters[key] - : prop - if (IS_CLIENT) { - const getters: string[] = - // @ts-expect-error: it should be on the store - setupStore._getters || (setupStore._getters = markRaw([])) - getters.push(key) - } - } - } - } - const partialStore = { _p: pinia, // _s: scope, @@ -470,10 +400,84 @@ function createSetupStore< _hmrPayload, } : {}, - partialStore, - setupStore + partialStore + // must be added later + // setupStore ) - ) as Store + ) as unknown as Store + + // store the partial store now so the setup of stores can use each other + pinia._s.set($id, store) + + // TODO: idea create skipSerialize that marks properties as non serializable and they are skipped + const setupStore = pinia._e.run(() => { + scope = effectScope() + return scope.run(() => setup()) + })! + + // overwrite existing actions to support $onAction + for (const key in setupStore) { + const prop = setupStore[key] + + if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) { + // mark it as a piece of state to be serialized + if (__DEV__ && hot) { + set(hotState.value, key, toRef(setupStore as any, key)) + // createOptionStore directly sets the state in pinia.state.value so we + // can just skip that + } else if (!buildState) { + if (isVue2) { + set(pinia.state.value[$id], key, prop) + } else { + pinia.state.value[$id][key] = prop + } + // TODO: avoid if state exists for SSR + } + + /* istanbul ignore else */ + if (__DEV__) { + _hmrPayload.state.push(key) + } + // action + } else if (typeof prop === 'function') { + // @ts-expect-error: we are overriding the function we avoid wrapping if + const actionValue = __DEV__ && hot ? prop : wrapAction(key, prop) + // this a hot module replacement store because the hotUpdate method needs + // to do it with the right context + if (isVue2) { + set(setupStore, key, actionValue) + } else { + // @ts-expect-error + setupStore[key] = actionValue + } + + /* istanbul ignore else */ + if (__DEV__) { + _hmrPayload.actions[key] = prop + } + + // list actions so they can be used in plugins + // @ts-expect-error + optionsForPlugin.actions[key] = prop + } else if (__DEV__) { + // add getters for devtools + if (isComputed(prop)) { + _hmrPayload.getters[key] = buildState + ? // @ts-expect-error + options.getters[key] + : prop + if (IS_CLIENT) { + const getters: string[] = + // @ts-expect-error: it should be on the store + setupStore._getters || (setupStore._getters = markRaw([])) + getters.push(key) + } + } + } + } + + // add the state, getters, and action properties + assign(store, setupStore) // use this instead of a computed with setter to be able to create it anywhere // without linking the computed lifespan to wherever the store is first @@ -778,13 +782,14 @@ export function defineStore( pinia = activePinia! if (!pinia._s.has(id)) { - pinia._s.set( - id, - isSetupStore - ? createSetupStore(id, setup, options, pinia) - : createOptionsStore(id, options as any, pinia) - ) + // creating the store registers it in `pinia._s` + if (isSetupStore) { + createSetupStore(id, setup, options, pinia) + } else { + createOptionsStore(id, options as any, pinia) + } + /* istanbul ignore else */ if (__DEV__) { // @ts-expect-error: not the right inferred type useStore._pinia = pinia