]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
feat: writable `computed`s to be picked up by `mapWritableState` (#2847)
authorNo Two <1244476905@qq.com>
Fri, 29 Nov 2024 10:16:41 +0000 (18:16 +0800)
committerGitHub <noreply@github.com>
Fri, 29 Nov 2024 10:16:41 +0000 (11:16 +0100)
packages/pinia/__tests__/mapHelpers.spec.ts
packages/pinia/src/mapHelpers.ts
packages/pinia/test-dts/mapHelpers.test-d.ts

index 11a5b415f254f1549630745bd48b0e936423c20b..9eb9e01fc7da1d76ba79a939baaa3297951dcc74 100644 (file)
@@ -10,7 +10,7 @@ import {
   setMapStoreSuffix,
 } from '../src'
 import { mount } from '@vue/test-utils'
-import { nextTick, defineComponent } from 'vue'
+import { nextTick, defineComponent, ref, computed } from 'vue'
 import { mockWarn } from './vitest-mock-warn'
 
 describe('Map Helpers', () => {
@@ -245,5 +245,27 @@ describe('Map Helpers', () => {
         'replaced replaced'
       )
     })
+
+    it('setup store', async () => {
+      const useSetupStore = defineStore('setup', () => {
+        const text = ref('initial')
+
+        const textUpper = computed({
+          get: () => text.value.toUpperCase(),
+          set: (v) => {
+            text.value = v
+          },
+        })
+
+        return { text, textUpper }
+      })
+
+      await testComponent(
+        mapWritableState(useSetupStore, ['text', 'textUpper']),
+        `{{ text }} {{ textUpper }}`,
+        `initial INITIAL`,
+        'replaced REPLACED'
+      )
+    })
   })
 })
index 4cb219008c3b5cf15c8d9de5ca1a6381c8a72a37..aa7662cb099e39456baed107242914239bbba3c4 100644 (file)
@@ -1,7 +1,7 @@
 import type { ComponentPublicInstance, ComputedRef, UnwrapRef } from 'vue-demi'
 import type {
   _GettersTree,
-  _Method,
+  _StoreWithGetters_Writable,
   StateTree,
   Store,
   StoreDefinition,
@@ -431,10 +431,21 @@ export function mapActions<
 /**
  * For internal use **only**
  */
-export type _MapWritableStateReturn<S> = {
-  [key in keyof S]: {
-    get: () => S[key]
-    set: (value: S[key]) => any
+export type _MapWritableStateKeys<S extends StateTree, G> =
+  | keyof UnwrapRef<S>
+  | keyof _StoreWithGetters_Writable<G>
+
+/**
+ * For internal use **only**
+ */
+export type _MapWritableStateReturn<
+  S extends StateTree,
+  G,
+  Keys extends _MapWritableStateKeys<S, G>,
+> = {
+  [key in Keys]: {
+    get: () => UnwrapRef<(S & G)[key]>
+    set: (value: UnwrapRef<(S & G)[key]>) => any
   }
 }
 
@@ -442,12 +453,13 @@ export type _MapWritableStateReturn<S> = {
  * For internal use **only**
  */
 export type _MapWritableStateObjectReturn<
-  S,
-  T extends Record<string, keyof S>,
+  S extends StateTree,
+  G,
+  KeyMapper extends Record<string, _MapWritableStateKeys<S, G>>,
 > = {
-  [key in keyof T]: {
-    get: () => S[T[key]]
-    set: (value: S[T[key]]) => any
+  [key in keyof KeyMapper]: {
+    get: () => UnwrapRef<(S & G)[KeyMapper[key]]>
+    set: (value: UnwrapRef<(S & G)[KeyMapper[key]]>) => any
   }
 }
 
@@ -462,13 +474,13 @@ export type _MapWritableStateObjectReturn<
 export function mapWritableState<
   Id extends string,
   S extends StateTree,
-  G extends _GettersTree<S>,
+  G,
   A,
-  KeyMapper extends Record<string, keyof UnwrapRef<S>>,
+  KeyMapper extends Record<string, _MapWritableStateKeys<S, G>>,
 >(
   useStore: StoreDefinition<Id, S, G, A>,
   keyMapper: KeyMapper
-): _MapWritableStateObjectReturn<UnwrapRef<S>, KeyMapper>
+): _MapWritableStateObjectReturn<S, G, 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
@@ -480,13 +492,13 @@ export function mapWritableState<
 export function mapWritableState<
   Id extends string,
   S extends StateTree,
-  G extends _GettersTree<S>,
+  G,
   A,
-  Keys extends keyof UnwrapRef<S>,
+  Keys extends _MapWritableStateKeys<S, G>,
 >(
   useStore: StoreDefinition<Id, S, G, A>,
   keys: readonly Keys[]
-): Pick<_MapWritableStateReturn<UnwrapRef<S>>, Keys>
+): Pick<_MapWritableStateReturn<S, G, Keys>, Keys>
 /**
  * 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
@@ -498,43 +510,51 @@ export function mapWritableState<
 export function mapWritableState<
   Id extends string,
   S extends StateTree,
-  G extends _GettersTree<S>,
+  G,
   A,
-  KeyMapper extends Record<string, keyof S>,
+  Keys extends _MapWritableStateKeys<S, G>,
+  KeyArr extends Keys[],
+  KeyMapper extends Record<string, Keys>,
 >(
   useStore: StoreDefinition<Id, S, G, A>,
-  keysOrMapper: Array<keyof S> | KeyMapper
-): _MapWritableStateReturn<S> | _MapWritableStateObjectReturn<S, KeyMapper> {
+  keysOrMapper: KeyArr | KeyMapper
+):
+  | _MapWritableStateReturn<S, G, Keys>
+  | _MapWritableStateObjectReturn<S, G, KeyMapper> {
   return Array.isArray(keysOrMapper)
-    ? keysOrMapper.reduce((reduced, key) => {
-        // @ts-ignore
-        reduced[key] = {
-          get(this: ComponentPublicInstance) {
-            // @ts-expect-error: FIXME: should work?
-            return useStore(this.$pinia)[key]
-          },
-          set(this: ComponentPublicInstance, value) {
-            // @ts-expect-error: FIXME: should work?
-            return (useStore(this.$pinia)[key] = value)
-          },
-        }
-        return reduced
-      }, {} as _MapWritableStateReturn<S>)
+    ? keysOrMapper.reduce(
+        (reduced, key) => {
+          reduced[key] = {
+            get(this: ComponentPublicInstance) {
+              return useStore(this.$pinia)[key] as (S & G)[typeof key]
+            },
+            set(
+              this: ComponentPublicInstance,
+              value: Store<Id, S, G, A>[typeof key]
+            ) {
+              return (useStore(this.$pinia)[key] = value)
+            },
+          }
+          return reduced
+        },
+        {} as _MapWritableStateReturn<S, G, Keys>
+      )
     : Object.keys(keysOrMapper).reduce(
         (reduced, key: keyof KeyMapper) => {
-          // @ts-ignore
           reduced[key] = {
             get(this: ComponentPublicInstance) {
-              // @ts-expect-error: FIXME: should work?
-              return useStore(this.$pinia)[keysOrMapper[key]]
+              return useStore(this.$pinia)[keysOrMapper[key]] as (S &
+                G)[KeyMapper[typeof key]]
             },
-            set(this: ComponentPublicInstance, value) {
-              // @ts-expect-error: FIXME: should work?
+            set(
+              this: ComponentPublicInstance,
+              value: Store<Id, S, G, A>[KeyMapper[typeof key]]
+            ) {
               return (useStore(this.$pinia)[keysOrMapper[key]] = value)
             },
           }
           return reduced
         },
-        {} as _MapWritableStateObjectReturn<S, KeyMapper>
+        {} as _MapWritableStateObjectReturn<S, G, KeyMapper>
       )
 }
index b9221246cea6268862d7b75eeefc17076d54997a..1d2ee3cb8a01fe88f2b4c8cdaa83c4c7451bb31f 100644 (file)
@@ -29,13 +29,17 @@ describe('mapHelpers', () => {
   const useSetupStore = defineStore('setupStore', () => {
     const a = ref('on' as 'on' | 'off')
     const upper = computed(() => a.value.toUpperCase())
+    const writableUpper = computed({
+      get: () => a.value.toUpperCase(),
+      set: (v: 'on' | 'off') => (a.value = v),
+    })
     function toggleA() {
       a.value = a.value === 'off' ? 'on' : 'off'
     }
     function setToggle(aVal: 'on' | 'off') {
       return (a.value = aVal)
     }
-    return { a, upper, toggleA, setToggle }
+    return { a, upper, writableUpper, toggleA, setToggle }
   })
 
   const useCounter = defineStore({
@@ -161,6 +165,13 @@ describe('mapHelpers', () => {
           set: (v: 'on' | 'off') => any
         }
       }>(mapWritableState(useSetupStore, ['a']))
+
+      expectTypeOf<{
+        writableUpper: {
+          get: () => string
+          set: (v: 'on' | 'off') => any
+        }
+      }>(mapWritableState(useSetupStore, ['writableUpper']))
     })
   })
 })