From: Eduardo San Martin Morote Date: Thu, 8 Apr 2021 16:22:30 +0000 (+0200) Subject: feat: mapWritableState X-Git-Tag: v0.3.0~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6eb04dae5cff92fc90a27a804c6dc54a2e25635c;p=thirdparty%2Fvuejs%2Fpinia.git feat: mapWritableState --- diff --git a/__tests__/mapHelpers.spec.ts b/__tests__/mapHelpers.spec.ts index e39c8508..bc109d8b 100644 --- a/__tests__/mapHelpers.spec.ts +++ b/__tests__/mapHelpers.spec.ts @@ -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: `

${template}

`, + 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' + ) + }) + }) }) diff --git a/src/index.ts b/src/index.ts index 72a7cb95..84d1944f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,7 @@ export { mapActions, mapStores, mapState, + mapWritableState, mapGetters, MapStoresCustomization, setMapStoreSuffix, diff --git a/src/mapHelpers.ts b/src/mapHelpers.ts index 42158fad..d7cd7f19 100644 --- a/src/mapHelpers.ts +++ b/src/mapHelpers.ts @@ -354,3 +354,98 @@ export function mapActions< return reduced }, {} as MapActionsObjectReturn) } + +type MapWritableStateReturn = { + [key in keyof S]: { + get: () => Store[key] + set: (value: Store[key]) => any + } +} + +type MapWritableStateObjectReturn< + S extends StateTree, + T extends Record +> = { + [key in keyof T]: { + get: () => Store[T[key]] + set: (value: Store[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 +>( + useStore: StoreDefinition, + keyMapper: KeyMapper +): MapWritableStateObjectReturn +/** + * 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( + useStore: StoreDefinition, + keys: Array +): MapWritableStateReturn +/** + * 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 +>( + useStore: StoreDefinition, + keysOrMapper: Array | KeyMapper +): MapWritableStateReturn | MapWritableStateObjectReturn { + 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) + : 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) +} diff --git a/test-dts/mapHelpers.test-d.ts b/test-dts/mapHelpers.test-d.ts index 0327ed8a..8b779992 100644 --- a/test-dts/mapHelpers.test-d.ts +++ b/test-dts/mapHelpers.test-d.ts @@ -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' })