From c1cc6dbe98ce1e1aee9e0370cae19bfd2b9f5fbb Mon Sep 17 00:00:00 2001 From: webfansplz <308241863@qq.com> Date: Tue, 19 Jul 2022 17:54:47 +0800 Subject: [PATCH 1/7] feat(useStorage): `mergeDefaults` option --- packages/core/useStorage/index.test.ts | 12 ++++++++++++ packages/core/useStorage/index.ts | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/packages/core/useStorage/index.test.ts b/packages/core/useStorage/index.test.ts index e8009a9717a..6ead3e7c190 100644 --- a/packages/core/useStorage/index.test.ts +++ b/packages/core/useStorage/index.test.ts @@ -331,4 +331,16 @@ describe('useStorage', () => { expect(storage.removeItem).toBeCalledWith(KEY) }) + + it('mergeDefaults option', async () => { + // object + storage.setItem(KEY, JSON.stringify({ a: 1 })) + const objectRef = useStorage(KEY, { a: 2, b: 3 }, storage, { mergeDefaults: true }) + expect(JSON.stringify(objectRef.value)).toBe(JSON.stringify({ a: 2, b: 3 })) + + // array + storage.setItem(KEY, JSON.stringify([1])) + const arrayRef = useStorage(KEY, [2], storage, { mergeDefaults: true }) + expect(JSON.stringify(arrayRef.value)).toBe(JSON.stringify([1, 2])) + }) }) diff --git a/packages/core/useStorage/index.ts b/packages/core/useStorage/index.ts index f4eb796b9ce..da63c14c58e 100644 --- a/packages/core/useStorage/index.ts +++ b/packages/core/useStorage/index.ts @@ -75,6 +75,13 @@ export interface UseStorageOptions extends ConfigurableEventFilter, Configura */ writeDefaults?: boolean + /** + * Merge the default value to the storage (Only support object and array type) + * + * @default false + */ + mergeDefaults?: boolean + /** * Custom data serialization */ @@ -121,6 +128,7 @@ export function useStorage deep = true, listenToStorageChanges = true, writeDefaults = true, + mergeDefaults = false, shallow, window = defaultWindow, eventFilter, @@ -186,6 +194,10 @@ export function useStorage storage!.setItem(key, serializer.write(rawInit)) return rawInit } + else if (!event && type === 'object' && mergeDefaults) { + const value = serializer.read(rawValue) + return Array.isArray(value) ? [...value, ...<[]>rawInit] : { ...value, ...rawInit } + } else if (typeof rawValue !== 'string') { return rawValue } From 2bd9199533739c812b08f57cd5d51259a9576bc5 Mon Sep 17 00:00:00 2001 From: webfansplz <308241863@qq.com> Date: Tue, 19 Jul 2022 18:14:39 +0800 Subject: [PATCH 2/7] chore: update --- packages/core/useStorage/index.test.ts | 5 +++++ packages/core/useStorage/index.ts | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/core/useStorage/index.test.ts b/packages/core/useStorage/index.test.ts index 6ead3e7c190..d92c51d8442 100644 --- a/packages/core/useStorage/index.test.ts +++ b/packages/core/useStorage/index.test.ts @@ -333,6 +333,11 @@ describe('useStorage', () => { }) it('mergeDefaults option', async () => { + // basic + storage.setItem(KEY, '0') + const basicRef = useStorage(KEY, 1, storage, { mergeDefaults: true }) + expect(basicRef.value).toBe(1) + // object storage.setItem(KEY, JSON.stringify({ a: 1 })) const objectRef = useStorage(KEY, { a: 2, b: 3 }, storage, { mergeDefaults: true }) diff --git a/packages/core/useStorage/index.ts b/packages/core/useStorage/index.ts index da63c14c58e..d07fa21ad8a 100644 --- a/packages/core/useStorage/index.ts +++ b/packages/core/useStorage/index.ts @@ -76,7 +76,7 @@ export interface UseStorageOptions extends ConfigurableEventFilter, Configura writeDefaults?: boolean /** - * Merge the default value to the storage (Only support object and array type) + * Merge the default value to the storage * * @default false */ @@ -194,9 +194,12 @@ export function useStorage storage!.setItem(key, serializer.write(rawInit)) return rawInit } - else if (!event && type === 'object' && mergeDefaults) { - const value = serializer.read(rawValue) - return Array.isArray(value) ? [...value, ...<[]>rawInit] : { ...value, ...rawInit } + else if (!event && mergeDefaults) { + if (type === 'object') { + const value = serializer.read(rawValue) + return Array.isArray(value) ? [...value, ...<[]>rawInit] : { ...value, ...rawInit } + } + return rawInit } else if (typeof rawValue !== 'string') { return rawValue From e5d7346dc83b5d480651f7591aa533034bbb8ee6 Mon Sep 17 00:00:00 2001 From: webfansplz <308241863@qq.com> Date: Tue, 19 Jul 2022 22:12:16 +0800 Subject: [PATCH 3/7] chore: update --- packages/core/useStorage/index.test.ts | 6 ++++++ packages/core/useStorage/index.ts | 13 ++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/core/useStorage/index.test.ts b/packages/core/useStorage/index.test.ts index d92c51d8442..b5c6a40a149 100644 --- a/packages/core/useStorage/index.test.ts +++ b/packages/core/useStorage/index.test.ts @@ -347,5 +347,11 @@ describe('useStorage', () => { storage.setItem(KEY, JSON.stringify([1])) const arrayRef = useStorage(KEY, [2], storage, { mergeDefaults: true }) expect(JSON.stringify(arrayRef.value)).toBe(JSON.stringify([1, 2])) + + // custom function + storage.setItem(KEY, JSON.stringify([{ a: 1 }])) + const initial = [{ a: 3 }] + const customRef = useStorage(KEY, initial, storage, { mergeDefaults: () => ([{ a: 2 }, ...initial]) }) + expect(JSON.stringify(customRef.value)).toBe(JSON.stringify([{ a: 2 }, { a: 3 }])) }) }) diff --git a/packages/core/useStorage/index.ts b/packages/core/useStorage/index.ts index d07fa21ad8a..45f42f3d7c8 100644 --- a/packages/core/useStorage/index.ts +++ b/packages/core/useStorage/index.ts @@ -1,5 +1,5 @@ import type { Awaitable, ConfigurableEventFilter, ConfigurableFlush, MaybeComputedRef, RemovableRef } from '@vueuse/shared' -import { pausableWatch, resolveUnref } from '@vueuse/shared' +import { isFunction, pausableWatch, resolveUnref } from '@vueuse/shared' import { ref, shallowRef } from 'vue-demi' import type { StorageLike } from '../ssr-handlers' import { getSSRHandler } from '../ssr-handlers' @@ -77,10 +77,12 @@ export interface UseStorageOptions extends ConfigurableEventFilter, Configura /** * Merge the default value to the storage + * Note: It'll be a shallow merge when set to true + * You can provide a custom function for deep merge * * @default false */ - mergeDefaults?: boolean + mergeDefaults?: boolean | ((storage: StorageLike, defaults: T) => T) /** * Custom data serialization @@ -195,10 +197,11 @@ export function useStorage return rawInit } else if (!event && mergeDefaults) { - if (type === 'object') { - const value = serializer.read(rawValue) + const value = serializer.read(rawValue) + if (isFunction(mergeDefaults)) + return mergeDefaults(storage!, value) + else if (type === 'object') return Array.isArray(value) ? [...value, ...<[]>rawInit] : { ...value, ...rawInit } - } return rawInit } else if (typeof rawValue !== 'string') { From e05f61e8bd5622f1e9a9b62ef72efdb28b7978de Mon Sep 17 00:00:00 2001 From: webfansplz <308241863@qq.com> Date: Tue, 19 Jul 2022 22:15:46 +0800 Subject: [PATCH 4/7] chore: update --- packages/core/useStorage/index.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/useStorage/index.test.ts b/packages/core/useStorage/index.test.ts index b5c6a40a149..48220ac5ff8 100644 --- a/packages/core/useStorage/index.test.ts +++ b/packages/core/useStorage/index.test.ts @@ -351,7 +351,7 @@ describe('useStorage', () => { // custom function storage.setItem(KEY, JSON.stringify([{ a: 1 }])) const initial = [{ a: 3 }] - const customRef = useStorage(KEY, initial, storage, { mergeDefaults: () => ([{ a: 2 }, ...initial]) }) - expect(JSON.stringify(customRef.value)).toBe(JSON.stringify([{ a: 2 }, { a: 3 }])) + const customRef = useStorage(KEY, initial, storage, { mergeDefaults: (_, value) => ([...initial, ...value]) }) + expect(JSON.stringify(customRef.value)).toBe(JSON.stringify([{ a: 3 }, { a: 1 }])) }) }) From 93b57d2a64ae98c2496b1dc1ee0517dac94265de Mon Sep 17 00:00:00 2001 From: webfansplz <308241863@qq.com> Date: Wed, 20 Jul 2022 09:26:02 +0800 Subject: [PATCH 5/7] chore: update --- packages/core/useStorage/index.test.ts | 3 +-- packages/core/useStorage/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core/useStorage/index.test.ts b/packages/core/useStorage/index.test.ts index 48220ac5ff8..e726efce839 100644 --- a/packages/core/useStorage/index.test.ts +++ b/packages/core/useStorage/index.test.ts @@ -350,8 +350,7 @@ describe('useStorage', () => { // custom function storage.setItem(KEY, JSON.stringify([{ a: 1 }])) - const initial = [{ a: 3 }] - const customRef = useStorage(KEY, initial, storage, { mergeDefaults: (_, value) => ([...initial, ...value]) }) + const customRef = useStorage(KEY, [{ a: 3 }], storage, { mergeDefaults: (value, initial) => ([...initial, ...value]) }) expect(JSON.stringify(customRef.value)).toBe(JSON.stringify([{ a: 3 }, { a: 1 }])) }) }) diff --git a/packages/core/useStorage/index.ts b/packages/core/useStorage/index.ts index 45f42f3d7c8..e183b858451 100644 --- a/packages/core/useStorage/index.ts +++ b/packages/core/useStorage/index.ts @@ -82,7 +82,7 @@ export interface UseStorageOptions extends ConfigurableEventFilter, Configura * * @default false */ - mergeDefaults?: boolean | ((storage: StorageLike, defaults: T) => T) + mergeDefaults?: boolean | ((storageValue: T, defaults: T) => T) /** * Custom data serialization @@ -199,7 +199,7 @@ export function useStorage else if (!event && mergeDefaults) { const value = serializer.read(rawValue) if (isFunction(mergeDefaults)) - return mergeDefaults(storage!, value) + return mergeDefaults(value, rawInit) else if (type === 'object') return Array.isArray(value) ? [...value, ...<[]>rawInit] : { ...value, ...rawInit } return rawInit From 4fc7327c4d669baedd9a984490716ab25fb89eb2 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 24 Jul 2022 17:45:17 +0800 Subject: [PATCH 6/7] chore: update --- packages/core/useStorage/index.md | 39 +++++++++++++++++++++++++++++++ packages/core/useStorage/index.ts | 30 +++++++++++------------- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/packages/core/useStorage/index.md b/packages/core/useStorage/index.md index 24103a8cd6a..95b3a0aacd6 100644 --- a/packages/core/useStorage/index.md +++ b/packages/core/useStorage/index.md @@ -32,6 +32,45 @@ const id = useStorage('my-id', 'some-string-id', sessionStorage) // returns Ref< state.value = null ``` +## Merge Defaults + +By default, `useStorage` will use the value from storage if it presents and ignores the default value. Be aware that when you adding more properties to the default value, the key might be undefined if client's storage does not have that key. + +```ts +localStorage.setItem('my-store', '{"hello": "hello"}') + +const state = useStorage('my-store', { hello: 'hi', greeting: 'hello' }, localStorage) + +console.log(state.greeting) // undefined, since the value is not presented in storage +``` + +To solve that, you can enable `mergeDefaults` option. + +```ts +localStorage.setItem('my-store', '{"hello": "nihao"}') + +const state = useStorage( + 'my-store', + { hello: 'hi', greeting: 'hello' }, + localStorage, + { mergeDefaults: true } // <-- +) + +console.log(state.hello) // 'nihao', from storage +console.log(state.greeting) // 'hello', from merged default value +``` + +When setting it to true, it will perform a **shallow merge** for objects/arrays. You can pass a custom merge function or deep merge, for example: + +```ts +const state = useStorage( + 'my-store', + { hello: 'hi', greeting: 'hello' }, + localStorage, + { mergeDefaults: (storageValue, defaults) => deepMerge(defaults, storageValue) } // <-- +) +``` + ## Custom Serialization By default, `useStorage` will smartly use the corresponding serializer based on the data type of provided default value. For example, `JSON.stringify` / `JSON.parse` will be used for objects, `Number.toString` / `parseFloat` for numbers, etc. diff --git a/packages/core/useStorage/index.ts b/packages/core/useStorage/index.ts index e183b858451..8f1abcbc06f 100644 --- a/packages/core/useStorage/index.ts +++ b/packages/core/useStorage/index.ts @@ -76,9 +76,10 @@ export interface UseStorageOptions extends ConfigurableEventFilter, Configura writeDefaults?: boolean /** - * Merge the default value to the storage - * Note: It'll be a shallow merge when set to true - * You can provide a custom function for deep merge + * Merge the default value with the value read from the storage. + * + * When setting to true, it will perform a shallow merge for objects/arrays. + * You can pass a custom merge function or deep merge. * * @default false */ @@ -104,24 +105,20 @@ export interface UseStorageOptions extends ConfigurableEventFilter, Configura shallow?: boolean } -export function useStorage(key: string, initialValue: MaybeComputedRef, storage?: StorageLike, options?: UseStorageOptions): RemovableRef -export function useStorage(key: string, initialValue: MaybeComputedRef, storage?: StorageLike, options?: UseStorageOptions): RemovableRef -export function useStorage(key: string, initialValue: MaybeComputedRef, storage?: StorageLike, options?: UseStorageOptions): RemovableRef -export function useStorage(key: string, initialValue: MaybeComputedRef, storage?: StorageLike, options?: UseStorageOptions): RemovableRef -export function useStorage(key: string, initialValue: MaybeComputedRef, storage?: StorageLike, options?: UseStorageOptions): RemovableRef +export function useStorage(key: string, defaults: MaybeComputedRef, storage?: StorageLike, options?: UseStorageOptions): RemovableRef +export function useStorage(key: string, defaults: MaybeComputedRef, storage?: StorageLike, options?: UseStorageOptions): RemovableRef +export function useStorage(key: string, defaults: MaybeComputedRef, storage?: StorageLike, options?: UseStorageOptions): RemovableRef +export function useStorage(key: string, defaults: MaybeComputedRef, storage?: StorageLike, options?: UseStorageOptions): RemovableRef +export function useStorage(key: string, defaults: MaybeComputedRef, storage?: StorageLike, options?: UseStorageOptions): RemovableRef /** * Reactive LocalStorage/SessionStorage. * * @see https://vueuse.org/useStorage - * @param key - * @param initialValue - * @param storage - * @param options */ export function useStorage( key: string, - initialValue: MaybeComputedRef, + defaults: MaybeComputedRef, storage: StorageLike | undefined, options: UseStorageOptions = {}, ): RemovableRef { @@ -138,7 +135,8 @@ export function useStorage console.error(e) }, } = options - const data = (shallow ? shallowRef : ref)(initialValue) as RemovableRef + + const data = (shallow ? shallowRef : ref)(defaults) as RemovableRef if (!storage) { try { @@ -152,7 +150,7 @@ export function useStorage if (!storage) return data - const rawInit: T = resolveUnref(initialValue) + const rawInit: T = resolveUnref(defaults) const type = guessSerializerType(rawInit) const serializer = options.serializer ?? StorageSerializers[type] @@ -201,7 +199,7 @@ export function useStorage if (isFunction(mergeDefaults)) return mergeDefaults(value, rawInit) else if (type === 'object') - return Array.isArray(value) ? [...value, ...<[]>rawInit] : { ...value, ...rawInit } + return Array.isArray(value) ? [...rawInit as any, ...value] : { ...rawInit as any, ...value } return rawInit } else if (typeof rawValue !== 'string') { From 985e72e847b6cb4a6944dec2881d93ddf5daf725 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 24 Jul 2022 18:04:57 +0800 Subject: [PATCH 7/7] chore: update --- packages/core/useStorage/index.md | 2 +- packages/core/useStorage/index.test.ts | 15 ++++++++++----- packages/core/useStorage/index.ts | 10 +++++----- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/core/useStorage/index.md b/packages/core/useStorage/index.md index 95b3a0aacd6..ff6fa241d94 100644 --- a/packages/core/useStorage/index.md +++ b/packages/core/useStorage/index.md @@ -60,7 +60,7 @@ console.log(state.hello) // 'nihao', from storage console.log(state.greeting) // 'hello', from merged default value ``` -When setting it to true, it will perform a **shallow merge** for objects/arrays. You can pass a custom merge function or deep merge, for example: +When setting it to true, it will perform a **shallow merge** for objects. You can pass a function to perform custom merge (e.g. deep merge), for example: ```ts const state = useStorage( diff --git a/packages/core/useStorage/index.test.ts b/packages/core/useStorage/index.test.ts index e726efce839..51b2e563c3d 100644 --- a/packages/core/useStorage/index.test.ts +++ b/packages/core/useStorage/index.test.ts @@ -336,21 +336,26 @@ describe('useStorage', () => { // basic storage.setItem(KEY, '0') const basicRef = useStorage(KEY, 1, storage, { mergeDefaults: true }) - expect(basicRef.value).toBe(1) + expect(basicRef.value).toBe(0) // object storage.setItem(KEY, JSON.stringify({ a: 1 })) const objectRef = useStorage(KEY, { a: 2, b: 3 }, storage, { mergeDefaults: true }) - expect(JSON.stringify(objectRef.value)).toBe(JSON.stringify({ a: 2, b: 3 })) + expect(objectRef.value).toEqual({ a: 1, b: 3 }) // array storage.setItem(KEY, JSON.stringify([1])) const arrayRef = useStorage(KEY, [2], storage, { mergeDefaults: true }) - expect(JSON.stringify(arrayRef.value)).toBe(JSON.stringify([1, 2])) + expect(arrayRef.value).toEqual([1]) // custom function storage.setItem(KEY, JSON.stringify([{ a: 1 }])) - const customRef = useStorage(KEY, [{ a: 3 }], storage, { mergeDefaults: (value, initial) => ([...initial, ...value]) }) - expect(JSON.stringify(customRef.value)).toBe(JSON.stringify([{ a: 3 }, { a: 1 }])) + const customRef = useStorage(KEY, [{ a: 3 }], storage, { mergeDefaults: (value, initial) => [...initial, ...value] }) + expect(customRef.value).toEqual([{ a: 3 }, { a: 1 }]) + + // custom function 2 + storage.setItem(KEY, '1') + const customRef2 = useStorage(KEY, 2, storage, { mergeDefaults: (value, initial) => value + initial }) + expect(customRef2.value).toEqual(3) }) }) diff --git a/packages/core/useStorage/index.ts b/packages/core/useStorage/index.ts index 8f1abcbc06f..f2ca68b9e52 100644 --- a/packages/core/useStorage/index.ts +++ b/packages/core/useStorage/index.ts @@ -78,8 +78,8 @@ export interface UseStorageOptions extends ConfigurableEventFilter, Configura /** * Merge the default value with the value read from the storage. * - * When setting to true, it will perform a shallow merge for objects/arrays. - * You can pass a custom merge function or deep merge. + * When setting it to true, it will perform a **shallow merge** for objects. + * You can pass a function to perform custom merge (e.g. deep merge), for example: * * @default false */ @@ -198,9 +198,9 @@ export function useStorage const value = serializer.read(rawValue) if (isFunction(mergeDefaults)) return mergeDefaults(value, rawInit) - else if (type === 'object') - return Array.isArray(value) ? [...rawInit as any, ...value] : { ...rawInit as any, ...value } - return rawInit + else if (type === 'object' && !Array.isArray(value)) + return { ...rawInit as any, ...value } + return value } else if (typeof rawValue !== 'string') { return rawValue