Skip to content

Commit

Permalink
fix: add effect scope support
Browse files Browse the repository at this point in the history
  • Loading branch information
kiaking committed Aug 18, 2021
1 parent 31dbe7a commit f3abade
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 13 deletions.
39 changes: 28 additions & 11 deletions src/store-util.js
@@ -1,4 +1,4 @@
import { reactive, watch } from 'vue'
import { reactive, computed, watch, effectScope } from 'vue'
import { forEachValue, isObject, isPromise, assert, partial } from './util'

export function genericSubscribe (fn, subs, options) {
Expand Down Expand Up @@ -29,30 +29,42 @@ export function resetStore (store, hot) {

export function resetStoreState (store, state, hot) {
const oldState = store._state
const oldScope = store._scope

// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const computedObj = {}
forEachValue(wrappedGetters, (fn, 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.
computedObj[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
// TODO: use `computed` when it's possible. at the moment we can't due to
// https://github.com/vuejs/vuex/pull/1883
get: () => computedObj[key](),
enumerable: true // for local getters
const computedCache = {}

// create a new effect scope and create computed object inside it to avoid
// getters (computed) getting destroyed on component unmount.
const scope = effectScope(true)

scope.run(() => {
forEachValue(wrappedGetters, (fn, 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.
computedObj[key] = partial(fn, store)
computedCache[key] = computed(() => computedObj[key]())
Object.defineProperty(store.getters, key, {
get: () => computedCache[key].value,
enumerable: true // for local getters
})
})
})

store._state = reactive({
data: state
})

// 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

// enable strict mode for new state
if (store.strict) {
enableStrictMode(store)
Expand All @@ -67,6 +79,11 @@ export function resetStoreState (store, state, hot) {
})
}
}

// dispose previously registered effect scope if there is one.
if (oldScope) {
oldScope.stop()
}
}

export function installModule (store, rootState, path, module, hot) {
Expand Down
6 changes: 6 additions & 0 deletions src/store.js
Expand Up @@ -39,6 +39,12 @@ export class Store {
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._makeLocalGettersCache = Object.create(null)

// EffectScope instance. when registering new getters, we wrap them inside
// EffectScope so that getters (computed) would not be destroyed on
// component unmount.
this._scope = null

this._devtools = devtools

// bind commit and dispatch to self
Expand Down
4 changes: 2 additions & 2 deletions test/unit/modules.spec.js
Expand Up @@ -126,7 +126,7 @@ describe('Modules', () => {
expect(mutationSpy).toHaveBeenCalled()
})

it.only('should keep getters when component gets destroyed', async () => {
it('should keep getters when component gets destroyed', async () => {
const store = new Vuex.Store()

const moduleA = {
Expand Down Expand Up @@ -165,7 +165,7 @@ describe('Modules', () => {
await nextTick()

store.commit('moduleA/increment')
console.log(store.state.moduleA.value)

expect(store.getters['moduleA/getState']).toBe(2)
})
})
Expand Down

0 comments on commit f3abade

Please sign in to comment.