From: Eduardo San Martin Morote Date: Fri, 13 Aug 2021 18:22:51 +0000 (+0200) Subject: feat: add storeToRefs X-Git-Tag: @pinia/nuxt@0.0.1~46 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=1548e9c08b45d451c0203a506a876ff639ed1684;p=thirdparty%2Fvuejs%2Fpinia.git feat: add storeToRefs Close #565 --- diff --git a/__tests__/storeToRefs.spec.ts b/__tests__/storeToRefs.spec.ts new file mode 100644 index 00000000..5aef1c3a --- /dev/null +++ b/__tests__/storeToRefs.spec.ts @@ -0,0 +1,100 @@ +import { computed, ref, ToRefs } from 'vue' +import { createPinia, defineStore, setActivePinia, storeToRefs } from '../src' + +describe('storeToRefs', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + function objectOfRefs>(o: O): ToRefs { + return Object.keys(o).reduce((newO, key) => { + // @ts-expect-error: we only need to match + newO[key] = expect.objectContaining({ value: o[key] }) + return newO + }, {} as ToRefs) + } + + it('empty state', () => { + expect(storeToRefs(defineStore('a', {})())).toEqual({}) + expect(storeToRefs(defineStore('a', () => {})())).toEqual({}) + expect(storeToRefs(defineStore({ id: 'a' })())).toEqual({}) + }) + + it('empty getters', () => { + expect( + storeToRefs( + defineStore('a', { + state: () => ({ n: 0 }), + })() + ) + ).toEqual(objectOfRefs({ n: 0 })) + expect( + storeToRefs( + defineStore('a', () => { + return { n: ref(0) } + })() + ) + ).toEqual(objectOfRefs({ n: 0 })) + }) + + it('contains getters', () => { + expect( + storeToRefs( + defineStore('a', { + state: () => ({ n: 1 }), + getters: { + double: (state) => state.n * 2, + }, + })() + ) + ).toEqual(objectOfRefs({ n: 1, double: 2 })) + expect( + storeToRefs( + defineStore('a', () => { + const n = ref(0) + const double = computed(() => n.value * 2) + return { n, double } + })() + ) + ).toEqual(objectOfRefs({ n: 1, double: 2 })) + }) + + it('contain plugin states', () => { + const pinia = createPinia() + // directly push because no app + pinia._p.push(() => ({ + // @ts-expect-error: cannot set a ref yet + pluginN: ref(20), + // should not appear in refs + shared: 10, + })) + setActivePinia(pinia) + + expect( + storeToRefs( + defineStore('a', { + state: () => ({ n: 0 }), + })() + ) + ).toEqual(objectOfRefs({ n: 0, pluginN: 20 })) + expect( + storeToRefs( + defineStore('a', () => { + return { n: ref(0) } + })() + ) + ).toEqual(objectOfRefs({ n: 0, pluginN: 20 })) + }) + + tds(() => { + const store1 = defineStore('a', () => { + const n = ref(0) + const double = computed(() => n.value * 2) + return { n, double } + })() + + storeToRefs(store1).double + }) + + function tds(_fn: Function) {} +}) diff --git a/src/index.ts b/src/index.ts index b15060c9..61bb863e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,6 +45,8 @@ export { setMapStoreSuffix, } from './mapHelpers' +export { storeToRefs } from './storeToRefs' + export type { MapStoresCustomization, _MapActionsObjectReturn, diff --git a/src/storeToRefs.ts b/src/storeToRefs.ts new file mode 100644 index 00000000..c9dbcd69 --- /dev/null +++ b/src/storeToRefs.ts @@ -0,0 +1,36 @@ +import { isReactive, isRef, toRaw, toRef, ToRefs } from 'vue-demi' +import { StoreGetters, StoreState } from './store' +import type { PiniaCustomStateProperties, StoreGeneric } from './types' + +/** + * Creates an object of references with all the state, getters, and plugin-added + * state properties of the store. Similar to `toRefs()` but specifically + * designed for Pinia stores so methods and non reactive properties are + * completely ignored. + * + * @param store - store to extract the refs from + */ +export function storeToRefs( + store: SS +): ToRefs< + StoreState & StoreGetters & PiniaCustomStateProperties> +> { + store = toRaw(store) + + const refs = {} as ToRefs< + StoreState & + StoreGetters & + PiniaCustomStateProperties> + > + for (const key in store) { + const value = store[key] + if (isRef(value) || isReactive(value)) { + // @ts-expect-error: the key is state or getter + refs[key] = + // --- + toRef(store, key) + } + } + + return refs +} diff --git a/src/types.ts b/src/types.ts index 388ae938..c5a5ff86 100644 --- a/src/types.ts +++ b/src/types.ts @@ -467,6 +467,7 @@ export type Store< (ActionsTree extends A ? {} : A) & PiniaCustomProperties & PiniaCustomStateProperties + /** * Generic and type-unsafe version of Store. Doesn't fail on access with * strings, making it much easier to write generic functions that do not care diff --git a/test-dts/customizations.test-d.ts b/test-dts/customizations.test-d.ts index a60c14f6..3990052e 100644 --- a/test-dts/customizations.test-d.ts +++ b/test-dts/customizations.test-d.ts @@ -4,6 +4,7 @@ import { defineStore, mapStores, ActionsTree, + storeToRefs, } from './' import { App, ref, Ref } from 'vue' @@ -129,3 +130,45 @@ pinia.use(({ options, store }) => { }, {} as Record any>) } }) + +expectType<{ myState: Ref; stateOnly: Ref }>( + storeToRefs(defineStore('a', {})()) +) + +expectType<{ + a: Ref + myState: Ref + stateOnly: Ref +}>( + // @ts-expect-error: no a + storeToRefs(defineStore('a', {})()) +) + +expectType<{ + $onAction: Ref + myState: Ref + stateOnly: Ref +}>( + // @ts-expect-error: doesn't add store methods + storeToRefs(defineStore('a', {})()) +) + +expectType<{ a: Ref; myState: Ref; stateOnly: Ref }>( + storeToRefs(defineStore('a', { state: () => ({ a: true }) })()) +) + +expectType<{ + n: Ref + double: Ref + myState: Ref + stateOnly: Ref +}>( + storeToRefs( + defineStore('a', { + state: () => ({ n: 1 }), + getters: { + double: (state) => state.n * 2, + }, + })() + ) +)