]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
feat: mapWritableState
authorEduardo San Martin Morote <posva13@gmail.com>
Thu, 8 Apr 2021 16:22:30 +0000 (18:22 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Fri, 9 Apr 2021 07:39:38 +0000 (09:39 +0200)
__tests__/mapHelpers.spec.ts
src/index.ts
src/mapHelpers.ts
test-dts/mapHelpers.test-d.ts

index e39c8508a008051bf096f08cde490d5b03a8e114..bc109d8b1f15e5b512b326ba7aa2474d88815090 100644 (file)
@@ -6,6 +6,7 @@ import {
   mapState,
   mapStores,
   PiniaPlugin,
+  mapWritableState,
   setMapStoreSuffix,
 } from '../src'
 import { createLocalVue, mount } from '@vue/test-utils'
@@ -249,4 +250,60 @@ describe('Map Helpers', () => {
       expect(wrapper.vm.set(4)).toBe(4)
     })
   })
+
+  describe('mapWritableState', () => {
+    async function testComponent(
+      computedProperties: any,
+      template: string,
+      expectedText: string,
+      expectedText2: string
+    ) {
+      const pinia = createPinia()
+      const Component = defineComponent({
+        template: `<p>${template}</p>`,
+        computed: {
+          ...computedProperties,
+        },
+        methods: Object.keys(computedProperties).reduce((methods, name) => {
+          // @ts-ignore
+          methods['set_' + name] = function (v: any) {
+            // @ts-ignore
+            this[name] = v
+          }
+          return methods
+        }, {}),
+      })
+
+      const wrapper = mount(Component, { localVue, pinia })
+
+      expect(wrapper.text()).toBe(expectedText)
+
+      for (const key in computedProperties) {
+        // @ts-ignore
+        wrapper.vm['set_' + key]('replaced')
+      }
+
+      await nextTick()
+
+      expect(wrapper.text()).toBe(expectedText2)
+    }
+
+    it('array', async () => {
+      await testComponent(
+        mapWritableState(useStore, ['n', 'a']),
+        `{{ n }} {{ a }}`,
+        `0 true`,
+        'replaced replaced'
+      )
+    })
+
+    it('object', async () => {
+      await testComponent(
+        mapWritableState(useStore, { count: 'n', myA: 'a' }),
+        `{{ count }} {{ myA }}`,
+        `0 true`,
+        'replaced replaced'
+      )
+    })
+  })
 })
index 72a7cb95f535addd5a28c6d11c9a2b6eaae4eb96..84d1944fdc67ad867d9889023c51d3cfb93f1c2f 100644 (file)
@@ -19,6 +19,7 @@ export {
   mapActions,
   mapStores,
   mapState,
+  mapWritableState,
   mapGetters,
   MapStoresCustomization,
   setMapStoreSuffix,
index 42158fad662f33b7fe1aefb3991aea6e99363c28..d7cd7f19a50b4ea817922a5de68999917b1e3ce9 100644 (file)
@@ -354,3 +354,98 @@ export function mapActions<
         return reduced
       }, {} as MapActionsObjectReturn<A, KeyMapper>)
 }
+
+type MapWritableStateReturn<S extends StateTree> = {
+  [key in keyof S]: {
+    get: () => Store<string, S, {}, {}>[key]
+    set: (value: Store<string, S, {}, {}>[key]) => any
+  }
+}
+
+type MapWritableStateObjectReturn<
+  S extends StateTree,
+  T extends Record<string, keyof S>
+> = {
+  [key in keyof T]: {
+    get: () => Store<string, S, {}, {}>[T[key]]
+    set: (value: Store<string, S, {}, {}>[T[key]]) => any
+  }
+}
+
+/**
+ * Same as `mapState()` but creates computed setters as well so the state can be
+ * modified. Differently from `mapState()`, only `state` properties can be
+ * added.
+ *
+ * @param useStore - store to map from
+ * @param keyMapper - object of state properties
+ */
+export function mapWritableState<
+  Id extends string,
+  S extends StateTree,
+  G,
+  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, A>(
+  useStore: StoreDefinition<Id, S, G, A>,
+  keys: Array<keyof S>
+): MapWritableStateReturn<S>
+/**
+ * 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,
+  A,
+  KeyMapper extends Record<string, keyof S>
+>(
+  useStore: StoreDefinition<Id, S, G, A>,
+  keysOrMapper: Array<keyof S> | KeyMapper
+): MapWritableStateReturn<S> | MapWritableStateObjectReturn<S, KeyMapper> {
+  return Array.isArray(keysOrMapper)
+    ? keysOrMapper.reduce((reduced, key) => {
+        reduced[key] = {
+          get(this: Vue) {
+            return getCachedStore(this, useStore)[key]
+          },
+          set(this: Vue, value) {
+            // it's easier to type it here as any
+            return (getCachedStore(this, useStore)[key] = value as any)
+          },
+        }
+        return reduced
+      }, {} as MapWritableStateReturn<S>)
+    : Object.keys(keysOrMapper).reduce((reduced, key: keyof KeyMapper) => {
+        // @ts-ignore
+        reduced[key] = {
+          get(this: Vue) {
+            return getCachedStore(this, useStore)[keysOrMapper[key]]
+          },
+          set(this: Vue, value) {
+            // it's easier to type it here as any
+            return (getCachedStore(this, useStore)[
+              keysOrMapper[key]
+            ] = value as any)
+          },
+        }
+        return reduced
+      }, {} as MapWritableStateObjectReturn<S, KeyMapper>)
+}
index 0327ed8a27fb22cd97080117e22c436104b5330b..8b77999268e4e68c2f3efba8ef8d14bd2297b3bf 100644 (file)
@@ -1,4 +1,11 @@
-import { defineStore, expectType, mapStores, mapActions, mapState } from '.'
+import {
+  defineStore,
+  expectType,
+  mapStores,
+  mapActions,
+  mapState,
+  mapWritableState,
+} from '.'
 
 const useStore = defineStore({
   id: 'name',
@@ -73,3 +80,22 @@ expectType<{
   newSetToggle: (a: 'on' | 'off') => 'on' | 'off'
   newToggleA: () => void
 }>(mapActions(useStore, { newSetToggle: 'setToggle', newToggleA: 'toggleA' }))
+
+expectType<{
+  a: {
+    get: () => 'on' | 'off'
+    set: (v: 'on' | 'off') => any
+  }
+}>(mapWritableState(useStore, ['a']))
+
+expectType<{
+  newA: {
+    get: () => 'on' | 'off'
+    set: (v: 'on' | 'off') => any
+  }
+}>(mapWritableState(useStore, { newA: 'a' }))
+
+// @ts-expect-error: cannot use a getter
+mapWritableState(useStore, ['upper'])
+// @ts-expect-error: cannot use a getter
+mapWritableState(useStore, { up: 'upper' })