]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
feat: add storeToRefs
authorEduardo San Martin Morote <posva13@gmail.com>
Fri, 13 Aug 2021 18:22:51 +0000 (20:22 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Fri, 13 Aug 2021 18:22:51 +0000 (20:22 +0200)
Close #565

__tests__/storeToRefs.spec.ts [new file with mode: 0644]
src/index.ts
src/storeToRefs.ts [new file with mode: 0644]
src/types.ts
test-dts/customizations.test-d.ts

diff --git a/__tests__/storeToRefs.spec.ts b/__tests__/storeToRefs.spec.ts
new file mode 100644 (file)
index 0000000..5aef1c3
--- /dev/null
@@ -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 extends Record<any, any>>(o: O): ToRefs<O> {
+    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<O>)
+  }
+
+  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) {}
+})
index b15060c96d634ee5e86e4d39b1c19d82e7521668..61bb863e9aa208bbbd1b2ece06074dd4b48b6764 100644 (file)
@@ -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 (file)
index 0000000..c9dbcd6
--- /dev/null
@@ -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<SS extends StoreGeneric>(
+  store: SS
+): ToRefs<
+  StoreState<SS> & StoreGetters<SS> & PiniaCustomStateProperties<StoreState<SS>>
+> {
+  store = toRaw(store)
+
+  const refs = {} as ToRefs<
+    StoreState<SS> &
+      StoreGetters<SS> &
+      PiniaCustomStateProperties<StoreState<SS>>
+  >
+  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
+}
index 388ae93830be67f03bcdf8cf6475983ccd63eb4d..c5a5ff86975dffe51e11ac6a15b6ed5510dd2b16 100644 (file)
@@ -467,6 +467,7 @@ export type Store<
   (ActionsTree extends A ? {} : A) &
   PiniaCustomProperties<Id, S, G, A> &
   PiniaCustomStateProperties<S>
+
 /**
  * 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
index a60c14f6ab902278ffa8e0c9c55e562ad89f9ca2..3990052e06dde7d5199110d99291e17391e68c94 100644 (file)
@@ -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<string, (...args: any[]) => any>)
   }
 })
+
+expectType<{ myState: Ref<number>; stateOnly: Ref<number> }>(
+  storeToRefs(defineStore('a', {})())
+)
+
+expectType<{
+  a: Ref<boolean>
+  myState: Ref<number>
+  stateOnly: Ref<number>
+}>(
+  // @ts-expect-error: no a
+  storeToRefs(defineStore('a', {})())
+)
+
+expectType<{
+  $onAction: Ref<unknown>
+  myState: Ref<number>
+  stateOnly: Ref<number>
+}>(
+  // @ts-expect-error: doesn't add store methods
+  storeToRefs(defineStore('a', {})())
+)
+
+expectType<{ a: Ref<boolean>; myState: Ref<number>; stateOnly: Ref<number> }>(
+  storeToRefs(defineStore('a', { state: () => ({ a: true }) })())
+)
+
+expectType<{
+  n: Ref<number>
+  double: Ref<number>
+  myState: Ref<number>
+  stateOnly: Ref<number>
+}>(
+  storeToRefs(
+    defineStore('a', {
+      state: () => ({ n: 1 }),
+      getters: {
+        double: (state) => state.n * 2,
+      },
+    })()
+  )
+)