]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
feat(mapState): accept functions
authorEduardo San Martin Morote <posva13@gmail.com>
Thu, 8 Apr 2021 12:46:16 +0000 (14:46 +0200)
committerEduardo San Martin Morote <posva@users.noreply.github.com>
Fri, 9 Apr 2021 11:08:56 +0000 (13:08 +0200)
__tests__/mapHelpers.spec.ts
src/mapHelpers.ts
test-dts/mapHelpers.test-d.ts

index 2ca4170f4cf755d6f4fe3a4692a7745b816f56ec..e39c8508a008051bf096f08cde490d5b03a8e114 100644 (file)
@@ -169,6 +169,33 @@ describe('Map Helpers', () => {
       )
     })
 
+    it('object with functions', async () => {
+      await testComponent(
+        mapState(useStore, { triple: (state) => (state.n + 1) * 3, myA: 'a' }),
+        `{{ triple }} {{ myA }}`,
+        `3 true`
+      )
+    })
+
+    it('uses component context', async () => {
+      const pinia = createPinia()
+      let vm
+      const Component = defineComponent({
+        template: `<p>{{ n }}</p>`,
+        computed: {
+          ...mapState(useStore, {
+            n(store) {
+              vm = this
+              return store.n
+            },
+          }),
+        },
+      })
+
+      const wrapper = mount(Component, { localVue, pinia })
+      expect(vm).toBe(wrapper.vm)
+    })
+
     it('getters', async () => {
       await testComponent(
         mapState(useStore, ['double', 'notA', 'a']),
index e67bd99480c71c0b1c8c65d1a80703f193e3bd33..42158fad662f33b7fe1aefb3991aea6e99363c28 100644 (file)
@@ -1,5 +1,6 @@
 import type Vue from 'vue'
 import {
+  GenericStore,
   GenericStoreDefinition,
   Method,
   StateTree,
@@ -112,11 +113,20 @@ type MapStateReturn<S extends StateTree, G> = {
 }
 
 type MapStateObjectReturn<
+  Id extends string,
   S extends StateTree,
   G,
-  T extends Record<string, keyof S | keyof G>
+  A,
+  T extends Record<
+    string,
+    keyof S | keyof G | ((store: Store<Id, S, G, A>) => any)
+  >
 > = {
-  [key in keyof T]: () => Store<string, S, G, {}>[T[key]]
+  [key in keyof T]: () => T[key] extends (store: GenericStore) => infer R
+    ? R
+    : T[key] extends keyof S | keyof G
+    ? Store<Id, S, G, A>[T[key]]
+    : never
 }
 
 /**
@@ -124,6 +134,9 @@ type MapStateObjectReturn<
  * API (`setup()`) by generating an object to be spread in the `computed` field
  * of a component. The values of the object are the state properties/getters
  * while the keys are the names of the resulting computed properties.
+ * Optionally, you can also pass a custom function that will receive the store
+ * as its first argument. Note that while it has access to the component
+ * instance via `this`, it won't be typed.
  *
  * @example
  * ```js
@@ -131,7 +144,15 @@ type MapStateObjectReturn<
  *   computed: {
  *     // other computed properties
  *     // useCounterStore has a state property named `count` and a getter `double`
- *     ...mapState(useCounterStore, { n: 'count', doubleN: '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() {
@@ -149,11 +170,14 @@ export function mapState<
   S extends StateTree,
   G,
   A,
-  KeyMapper extends Record<string, keyof S | keyof G>
+  KeyMapper extends Record<
+    string,
+    keyof S | keyof G | ((store: Store<Id, S, G, A>) => any)
+  >
 >(
   useStore: StoreDefinition<Id, S, G, A>,
   keyMapper: KeyMapper
-): MapStateObjectReturn<S, G, 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
@@ -194,11 +218,14 @@ export function mapState<
   S extends StateTree,
   G,
   A,
-  KeyMapper extends Record<string, keyof S | keyof G>
+  KeyMapper extends Record<
+    string,
+    keyof S | keyof G | ((store: Store<Id, S, G, A>) => any)
+  >
 >(
   useStore: StoreDefinition<Id, S, G, A>,
   keysOrMapper: Array<keyof S | keyof G> | KeyMapper
-): MapStateReturn<S, G> | MapStateObjectReturn<S, G, KeyMapper> {
+): MapStateReturn<S, G> | MapStateObjectReturn<Id, S, G, A, KeyMapper> {
   return Array.isArray(keysOrMapper)
     ? keysOrMapper.reduce((reduced, key) => {
         reduced[key] = function (this: Vue) {
@@ -208,10 +235,16 @@ export function mapState<
       }, {} as MapStateReturn<S, G>)
     : Object.keys(keysOrMapper).reduce((reduced, key: keyof KeyMapper) => {
         reduced[key] = function (this: Vue) {
-          return getCachedStore(this, useStore)[keysOrMapper[key]]
+          const store = getCachedStore(this, useStore)
+          const storeKey = keysOrMapper[key]
+          // for some reason TS is unable to infer the type of storeKey to be a
+          // function
+          return typeof storeKey === 'function'
+            ? (storeKey as (store: Store<Id, S, G, A>) => any).call(this, store)
+            : store[storeKey as keyof S | keyof G]
         }
         return reduced
-      }, {} as MapStateObjectReturn<S, G, KeyMapper>)
+      }, {} as MapStateObjectReturn<Id, S, G, A, KeyMapper>)
 }
 
 /**
index 7fe826eb4e126e6ebd9485b252774c3452dfa6ae..0327ed8a27fb22cd97080117e22c436104b5330b 100644 (file)
@@ -51,6 +51,19 @@ expectType<{
   newUpper: () => string
 }>(mapState(useStore, { newA: 'a', newUpper: 'upper' }))
 
+expectType<{
+  newA: () => 'on' | 'off'
+  newUpper: () => string
+}>(
+  mapState(useStore, {
+    newA: (store) => {
+      expectType<string>(store.upper)
+      return store.a
+    },
+    newUpper: 'upper',
+  })
+)
+
 expectType<{
   setToggle: (a: 'on' | 'off') => 'on' | 'off'
   toggleA: () => void