Skip to content

Commit 2a374ea

Browse files
committed
Add support for preserveState options
1 parent c9dbb13 commit 2a374ea

File tree

5 files changed

+93
-6
lines changed

5 files changed

+93
-6
lines changed

docs/guide/modules.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,10 @@ It may be likely that you want to preserve the previous state when registering a
321321

322322
When you set `preserveState: true`, the module is registered, actions, mutations and getters are added to the store, but the state is not. It's assumed that your store state already contains state for that module and you don't want to overwrite it.
323323

324+
If you want state to be preserved only if the existing state actually exists, you can set the `preserveStateType` to `existing`. In this case, the state from the module will be registered if the existing state does not exist.
325+
326+
You can also set `preserveStateType` to `mergeReplaceArrays` or `mergeConcatArrays`, to merge the existing state with the module state using `deepmerge`, either by replacing arrays or combining them. Individual keys from the existing state will overwrite the default ones from the module.
327+
324328
## Module Reuse
325329

326330
Sometimes we may need to create multiple instances of a module, for example:

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,8 @@
9191
"webpack": "^4.43.0",
9292
"webpack-dev-middleware": "^3.7.2",
9393
"webpack-hot-middleware": "^2.25.0"
94+
},
95+
"dependencies": {
96+
"deepmerge": "^4.2.2"
9497
}
9598
}

src/store.js

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import applyMixin from './mixin'
22
import devtoolPlugin from './plugins/devtool'
33
import ModuleCollection from './module/module-collection'
44
import { forEachValue, isObject, isPromise, assert, partial } from './util'
5+
import deepmerge from 'deepmerge'
56

67
let Vue // bind on install
78

@@ -209,7 +210,7 @@ export class Store {
209210
}
210211

211212
this._modules.register(path, rawModule)
212-
installModule(this, this.state, path, this._modules.get(path), options.preserveState)
213+
installModule(this, this.state, path, this._modules.get(path), options.preserveState, options.preserveStateType)
213214
// reset store to update getters...
214215
resetStoreVM(this, this.state)
215216
}
@@ -328,7 +329,7 @@ function resetStoreVM (store, state, hot) {
328329
}
329330
}
330331

331-
function installModule (store, rootState, path, module, hot) {
332+
function installModule (store, rootState, path, module, preserveState, preserveStateType = 'always') {
332333
const isRoot = !path.length
333334
const namespace = store._modules.getNamespace(path)
334335

@@ -341,9 +342,15 @@ function installModule (store, rootState, path, module, hot) {
341342
}
342343

343344
// set state
344-
if (!isRoot && !hot) {
345-
const parentState = getNestedState(rootState, path.slice(0, -1))
346-
const moduleName = path[path.length - 1]
345+
const parentState = getNestedState(rootState, path.slice(0, -1))
346+
const moduleName = path[path.length - 1]
347+
const moduleStateExists = moduleName && moduleName in parentState
348+
if (!isRoot && (
349+
!preserveState ||
350+
(preserveStateType === 'existing' && !moduleStateExists) ||
351+
(preserveStateType === 'mergeReplaceArrays' && !moduleStateExists) ||
352+
(preserveStateType === 'mergeConcatArrays' && !moduleStateExists)
353+
)) {
347354
store._withCommit(() => {
348355
if (__DEV__) {
349356
if (moduleName in parentState) {
@@ -352,10 +359,28 @@ function installModule (store, rootState, path, module, hot) {
352359
)
353360
}
354361
}
362+
355363
Vue.set(parentState, moduleName, module.state)
356364
})
357365
}
358366

367+
// merge stored state with default state, replace arrays
368+
if (!isRoot && preserveState && preserveStateType === 'mergeReplaceArrays' && moduleStateExists) {
369+
const moduleOriginalState = parentState[moduleName]
370+
371+
store._withCommit(() => {
372+
Vue.set(parentState, moduleName, deepmerge(module.state, moduleOriginalState, { arrayMerge: (target, source, options) => source }))
373+
})
374+
}
375+
376+
// merge stored state with default state, concat arrays
377+
if (!isRoot && preserveState && preserveStateType === 'mergeConcatArrays' && moduleStateExists) {
378+
const moduleOriginalState = parentState[moduleName]
379+
store._withCommit(() => {
380+
Vue.set(parentState, moduleName, deepmerge(module.state, moduleOriginalState, { arrayMerge: (target, source, options) => target.concat(...source) }))
381+
})
382+
}
383+
359384
const local = module.context = makeLocalContext(store, namespace, path)
360385

361386
module.forEachMutation((mutation, key) => {
@@ -375,7 +400,7 @@ function installModule (store, rootState, path, module, hot) {
375400
})
376401

377402
module.forEachChild((child, key) => {
378-
installModule(store, rootState, path.concat(key), child, hot)
403+
installModule(store, rootState, path.concat(key), child, preserveState)
379404
})
380405
}
381406

test/unit/modules.spec.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,60 @@ describe('Modules', () => {
124124
store.commit('a/foo')
125125
expect(mutationSpy).toHaveBeenCalled()
126126
})
127+
128+
it('dynamic module registration ignoring hydration if it does not exist', () => {
129+
const store = new Vuex.Store({})
130+
131+
store.registerModule('test', {
132+
namespaced: true,
133+
state: () => ({ data: '12345' })
134+
}, { preserveState: true, preserveStateType: 'existing' })
135+
136+
expect(store.state.test.data).toBe('12345')
137+
})
138+
139+
it('dynamic module registration merging hydration', () => {
140+
const store = new Vuex.Store({})
141+
store.replaceState({ test: {
142+
foo: 'hello'
143+
}})
144+
145+
store.registerModule('test', {
146+
namespaced: true,
147+
state: () => ({ bar: 'world' })
148+
}, { preserveState: true, preserveStateType: 'mergeReplaceArrays' })
149+
150+
expect(store.state.test.foo).toBe('hello')
151+
expect(store.state.test.bar).toBe('world')
152+
})
153+
154+
it('dynamic module registration merging hydration by replacing arrays', () => {
155+
const store = new Vuex.Store({})
156+
store.replaceState({ test: {
157+
data: ['hello', 'world']
158+
}})
159+
160+
store.registerModule('test', {
161+
namespaced: true,
162+
state: () => ({ data: ['foo', 'bar'] })
163+
}, { preserveState: true, preserveStateType: 'mergeReplaceArrays' })
164+
165+
expect(store.state.test.data).toEqual(['hello', 'world'])
166+
})
167+
168+
it('dynamic module registration merging hydration by concating arrays', () => {
169+
const store = new Vuex.Store({})
170+
store.replaceState({ test: {
171+
data: ['hello', 'world']
172+
}})
173+
174+
store.registerModule('test', {
175+
namespaced: true,
176+
state: () => ({ data: ['foo', 'bar'] })
177+
}, { preserveState: true, preserveStateType: 'mergeConcatArrays' })
178+
179+
expect(store.state.test.data).toEqual(['foo', 'bar', 'hello', 'world'])
180+
})
127181
})
128182

129183
// #524

types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export interface Module<S, R> {
131131

132132
export interface ModuleOptions {
133133
preserveState?: boolean;
134+
preserveStateType?: 'always' | 'existing' | 'mergeReplaceArrays' | 'mergeConcatArrays';
134135
}
135136

136137
export interface GetterTree<S, R> {

0 commit comments

Comments
 (0)