From: Eduardo San Martin Morote Date: Mon, 19 Jul 2021 14:50:49 +0000 (+0200) Subject: feat(hmr): skip arrays X-Git-Tag: v2.0.0-rc.0~54 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1839a25bcefb1a887f33549e260da5bae5be37ab;p=thirdparty%2Fvuejs%2Fpinia.git feat(hmr): skip arrays --- diff --git a/__tests__/hmr.spec.ts b/__tests__/hmr.spec.ts index 68f08390..b87136a0 100644 --- a/__tests__/hmr.spec.ts +++ b/__tests__/hmr.spec.ts @@ -18,6 +18,13 @@ describe('HMR', () => { id: 'main', state: () => ({ n: 0, + arr: [], + nestedArr: { + arr: [], + }, + nested: { + a: 'a', + }, }), actions: { @@ -63,25 +70,62 @@ describe('HMR', () => { expect(store.other).toBe('new') }) - it('adds new state properties', () => { + it('patches nested objects', () => { const useStore = defineStore(baseOptions) const store: any = useStore() - store.n++ // simulate a hmr defineStore({ ...baseOptions, - state: () => ({ newOne: 'hey', n: 0 }), + state: () => ({ nested: { a: 'b', b: 'b' } }), })(null, store) - expect(store.$state).toEqual({ n: 1, newOne: 'hey' }) - expect(store.n).toBe(1) - expect(store.newOne).toBe('hey') + expect(store.$state).toEqual({ nested: { a: 'a', b: 'b' } }) defineStore({ ...baseOptions, - state: () => ({ other: 'new', n: 0 }), + state: () => ({ nested: { b: 'c' } }), + })(null, store) + // removes the nested a + expect(store.$state).toEqual({ nested: { b: 'b' } }) + }) + + it('skips arrays', () => { + const useStore = defineStore(baseOptions) + const store: any = useStore() + + // simulate a hmr + defineStore({ + ...baseOptions, + state: () => ({ arr: [2] }), + })(null, store) + + expect(store.$state).toEqual({ arr: [] }) + + defineStore({ + ...baseOptions, + state: () => ({ arr: [1] }), + })(null, store) + expect(store.$state).toEqual({ arr: [] }) + }) + + it('skips nested arrays', () => { + const useStore = defineStore(baseOptions) + const store: any = useStore() + + // simulate a hmr + defineStore({ + ...baseOptions, + state: () => ({ nestedArr: { arr: [2] } }), + })(null, store) + + expect(store.$state).toEqual({ nestedArr: { arr: [] } }) + + defineStore({ + ...baseOptions, + state: () => ({ nestedArr: { arr: [1] } }), })(null, store) + expect(store.$state).toEqual({ nestedArr: { arr: [] } }) }) it('keeps state reactive', () => { diff --git a/src/hmr.ts b/src/hmr.ts index ecd1cf1a..68b37b97 100644 --- a/src/hmr.ts +++ b/src/hmr.ts @@ -1,10 +1,42 @@ +import { isRef, isReactive } from 'vue' import { Pinia } from './rootStore' -import { Store, StoreDefinition, _Method } from './types' +import { isPlainObject, Store, StoreDefinition, _Method } from './types' export const isUseStore = (fn: any): fn is StoreDefinition => { return typeof fn === 'function' && typeof fn.$id === 'string' } +export function patchObject( + newState: Record, + oldState: Record +): Record { + // no need to go through symbols because they cannot be serialized anyway + for (const key in oldState) { + const subPatch = oldState[key] + + // skip the whole sub tree + if (!(key in newState)) { + continue + } + + const targetValue = newState[key] + if ( + isPlainObject(targetValue) && + isPlainObject(subPatch) && + !isRef(subPatch) && + !isReactive(subPatch) + ) { + newState[key] = patchObject(targetValue, subPatch) + } else { + // objects are either a bit more complex (e.g. refs) or primitives, so we + // just set the whole thing + newState[key] = subPatch + } + } + + return newState +} + export function acceptHMRUpdate( initialUseStore: StoreDefinition, hot: any diff --git a/src/store.ts b/src/store.ts index 06937406..51f3999b 100644 --- a/src/store.ts +++ b/src/store.ts @@ -45,6 +45,7 @@ import { activePinia, } from './rootStore' import { IS_CLIENT } from './env' +import { patchObject } from './hmr' function innerPatch( target: T, @@ -462,11 +463,20 @@ function createSetupStore< newStore._hmrPayload.state.forEach((stateKey) => { if (stateKey in store.$state) { // @ts-expect-error - // transfer the ref - newStore.$state[stateKey] = - // --- + const newStateTarget = newStore.$state[stateKey] + // @ts-expect-error + const oldStateSource = store.$state[stateKey] + if ( + typeof newStateTarget === 'object' && + isPlainObject(newStateTarget) && + isPlainObject(oldStateSource) + ) { + patchObject(newStateTarget, oldStateSource) + } else { // @ts-expect-error - store.$state[stateKey] + // transfer the ref + newStore.$state[stateKey] = oldStateSource + } } // patch direct access properties to allow store.stateProperty to work as // store.$state.stateProperty