diff --git a/src/store-util.js b/src/store-util.js index e3ba18609..c138a49f9 100644 --- a/src/store-util.js +++ b/src/store-util.js @@ -30,6 +30,8 @@ export function resetStore (store, hot) { export function resetStoreState (store, state, hot) { const oldState = store._state const oldScope = store._scope + const oldCache = store._computedCache + const oldGettersKeySet = new Set(store.getters ? Object.keys(store.getters) : []) // bind store public getters store.getters = {} @@ -45,6 +47,10 @@ export function resetStoreState (store, state, hot) { scope.run(() => { forEachValue(wrappedGetters, (fn, key) => { + // Filter stale getters' key by comparing oldGetters and wrappedGetters, + // the key does not be removed from oldGettersKeySet are the key of stale computed cache. + // Stale computed cache: the computed cache should be removed as the corresponding module is removed. + oldGettersKeySet.delete(key) // use computed to leverage its lazy-caching mechanism // direct inline function use will lead to closure preserving oldState. // using partial to return function with only arguments preserved in closure environment. @@ -64,6 +70,7 @@ export function resetStoreState (store, state, hot) { // register the newly created effect scope to the store so that we can // dispose the effects when this method runs again in the future. store._scope = scope + store._computedCache = computedCache // enable strict mode for new state if (store.strict) { @@ -82,6 +89,24 @@ export function resetStoreState (store, state, hot) { // dispose previously registered effect scope if there is one. if (oldScope) { + const deadEffects = [] + const staleComputedCache = new Set() + oldGettersKeySet.forEach((staleKey) => { + staleComputedCache.add(oldCache[staleKey]) + }) + oldScope.effects.forEach(effect => { + // Use the staleComputedCache match the computed property of reactiveEffect, + // to specify the stale cache + if (effect.deps.length && !staleComputedCache.has(effect.computed)) { + // Merge the effect that already have dependencies and prevent from being killed. + scope.effects.push(effect) + } else { + // Collect the dead effects. + deadEffects.push(effect) + } + }) + // Dispose the dead effects. + oldScope.effects = deadEffects oldScope.stop() } } diff --git a/test/unit/modules.spec.js b/test/unit/modules.spec.js index 9c6e96e46..7eae663dc 100644 --- a/test/unit/modules.spec.js +++ b/test/unit/modules.spec.js @@ -1,4 +1,4 @@ -import { h, nextTick } from 'vue' +import { computed, h, nextTick } from 'vue' import { mount } from 'test/helpers' import Vuex from '@/index' @@ -925,4 +925,31 @@ describe('Modules', () => { /getters should be function but "getters\.test" in module "foo\.bar" is true/ ) }) + + it('module: computed getter should be reactive after module registration', () => { + const store = new Vuex.Store({ + state: { + foo: 0 + }, + getters: { + getFoo: state => state.foo + }, + mutations: { + incrementFoo: state => state.foo++ + } + }) + + const computedFoo = computed(() => store.getters.getFoo) + store.commit('incrementFoo') + expect(computedFoo.value).toBe(1) + + store.registerModule('bar', { + state: { + bar: 0 + } + }) + + store.commit('incrementFoo') + expect(computedFoo.value).toBe(2) + }) })