diff --git a/package.json b/package.json index 3aae30a88..b6899b03e 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ }, "homepage": "https://github.com/vuejs/vuex#readme", "peerDependencies": { - "vue": "^3.0.2" + "vue": "^3.2.0" }, "dependencies": { "@vue/devtools-api": "^6.0.0-beta.11" @@ -65,7 +65,7 @@ "@rollup/plugin-node-resolve": "^13.0.0", "@rollup/plugin-replace": "^2.4.2", "@types/node": "^15.6.0", - "@vue/compiler-sfc": "^3.0.11", + "@vue/compiler-sfc": "^3.2.4", "babel-jest": "^26.6.3", "babel-loader": "^8.2.2", "brotli": "^1.3.2", @@ -89,8 +89,8 @@ "todomvc-app-css": "^2.4.1", "typescript": "^4.2.4", "vitepress": "^0.11.5", - "vue": "^3.0.11", - "vue-loader": "^16.2.0", + "vue": "^3.2.4", + "vue-loader": "^16.5.0", "vue-style-loader": "^4.1.3", "webpack": "^4.43.0", "webpack-dev-middleware": "^3.7.2", diff --git a/src/store-util.js b/src/store-util.js index cea5d8b02..e3ba18609 100644 --- a/src/store-util.js +++ b/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) { @@ -29,6 +29,7 @@ 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 = {} @@ -36,16 +37,23 @@ export function resetStoreState (store, state, hot) { 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 + }) }) }) @@ -53,6 +61,10 @@ export function resetStoreState (store, state, hot) { 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) @@ -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) { diff --git a/src/store.js b/src/store.js index 3ae6898f9..e22c74b5d 100644 --- a/src/store.js +++ b/src/store.js @@ -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 diff --git a/test/helpers.js b/test/helpers.js index 004fabcc7..1c6b82b52 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -4,7 +4,7 @@ import puppeteer from 'puppeteer' export function mount (store, component) { const el = createElement() - component.render = () => {} + component.render = component.render || (() => {}) const app = createApp(component) diff --git a/test/unit/modules.spec.js b/test/unit/modules.spec.js index 17a09857b..9c6e96e46 100644 --- a/test/unit/modules.spec.js +++ b/test/unit/modules.spec.js @@ -1,4 +1,5 @@ -import { nextTick } from 'vue' +import { h, nextTick } from 'vue' +import { mount } from 'test/helpers' import Vuex from '@/index' const TEST = 'TEST' @@ -124,6 +125,56 @@ describe('Modules', () => { store.commit('a/foo') expect(mutationSpy).toHaveBeenCalled() }) + + it('should keep getters when component gets destroyed', async () => { + const store = new Vuex.Store() + + const spy = jest.fn() + + const moduleA = { + namespaced: true, + state: () => ({ value: 1 }), + getters: { + getState (state) { + spy() + return state.value + } + }, + mutations: { + increment: (state) => { state.value++ } + } + } + + const CompA = { + template: `
`, + created () { + this.$store.registerModule('moduleA', moduleA) + } + } + + const CompB = { + template: `
` + } + + const vm = mount(store, { + components: { CompA, CompB }, + data: () => ({ show: 'a' }), + render () { + return this.show === 'a' ? h(CompA) : h(CompB) + } + }) + + expect(store.getters['moduleA/getState']).toBe(1) + expect(spy).toHaveBeenCalledTimes(1) + + vm.show = 'b' + await nextTick() + + store.commit('moduleA/increment') + + expect(store.getters['moduleA/getState']).toBe(2) + expect(spy).toHaveBeenCalledTimes(2) + }) }) // #524 diff --git a/yarn.lock b/yarn.lock index 2d3fddc67..08b05bc97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1659,6 +1659,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@^0.0.48": + version "0.0.48" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.48.tgz#18dc8091b285df90db2f25aa7d906cfc394b7f74" + integrity sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew== + "@types/graceful-fs@^4.1.2": version "4.1.4" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.4.tgz#4ff9f641a7c6d1a3508ff88bc3141b152772e753" @@ -1751,17 +1756,6 @@ resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-1.1.4.tgz#1dd388519b75439b7733601b55238ca691864796" integrity sha512-cUDILd++9jdhdjpuhgJofQqOabOKe+kTWTE2HQY2PBHEUO2fgwTurLE0cJg9UcIo1x4lHfsp+59S9TBCHgTZkw== -"@vue/compiler-core@3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.11.tgz#5ef579e46d7b336b8735228758d1c2c505aae69a" - integrity sha512-6sFj6TBac1y2cWCvYCA8YzHJEbsVkX7zdRs/3yK/n1ilvRqcn983XvpBbnN3v4mZ1UiQycTvOiajJmOgN9EVgw== - dependencies: - "@babel/parser" "^7.12.0" - "@babel/types" "^7.12.0" - "@vue/shared" "3.0.11" - estree-walker "^2.0.1" - source-map "^0.6.1" - "@vue/compiler-core@3.0.5": version "3.0.5" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.5.tgz#a6e54cabe9536e74c6513acd2649f311af1d43ac" @@ -1773,13 +1767,16 @@ estree-walker "^2.0.1" source-map "^0.6.1" -"@vue/compiler-dom@3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.0.11.tgz#b15fc1c909371fd671746020ba55b5dab4a730ee" - integrity sha512-+3xB50uGeY5Fv9eMKVJs2WSRULfgwaTJsy23OIltKgMrynnIj8hTYY2UL97HCoz78aDw1VDXdrBQ4qepWjnQcw== +"@vue/compiler-core@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.4.tgz#a98d295771998c1e8dccc4ee3d52feb14b02aea9" + integrity sha512-c8NuQq7mUXXxA4iqD5VUKpyVeklK53+DMbojYMyZ0VPPrb0BUWrZWFiqSDT+MFDv0f6Hv3QuLiHWb1BWMXBbrw== dependencies: - "@vue/compiler-core" "3.0.11" - "@vue/shared" "3.0.11" + "@babel/parser" "^7.12.0" + "@babel/types" "^7.12.0" + "@vue/shared" "3.2.4" + estree-walker "^2.0.1" + source-map "^0.6.1" "@vue/compiler-dom@3.0.5": version "3.0.5" @@ -1789,27 +1786,13 @@ "@vue/compiler-core" "3.0.5" "@vue/shared" "3.0.5" -"@vue/compiler-sfc@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.0.11.tgz#cd8ca2154b88967b521f5ad3b10f5f8b6b665679" - integrity sha512-7fNiZuCecRleiyVGUWNa6pn8fB2fnuJU+3AGjbjl7r1P5wBivfl02H4pG+2aJP5gh2u+0wXov1W38tfWOphsXw== +"@vue/compiler-dom@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.4.tgz#3a43de243eba127abbe57e796a0b969d2df78c08" + integrity sha512-uj1nwO4794fw2YsYas5QT+FU/YGrXbS0Qk+1c7Kp1kV7idhZIghWLTjyvYibpGoseFbYLPd+sW2/noJG5H04EQ== dependencies: - "@babel/parser" "^7.13.9" - "@babel/types" "^7.13.0" - "@vue/compiler-core" "3.0.11" - "@vue/compiler-dom" "3.0.11" - "@vue/compiler-ssr" "3.0.11" - "@vue/shared" "3.0.11" - consolidate "^0.16.0" - estree-walker "^2.0.1" - hash-sum "^2.0.0" - lru-cache "^5.1.1" - magic-string "^0.25.7" - merge-source-map "^1.1.0" - postcss "^8.1.10" - postcss-modules "^4.0.0" - postcss-selector-parser "^6.0.4" - source-map "^0.6.1" + "@vue/compiler-core" "3.2.4" + "@vue/shared" "3.2.4" "@vue/compiler-sfc@^3.0.5": version "3.0.5" @@ -1833,13 +1816,28 @@ postcss-selector-parser "^6.0.4" source-map "^0.6.1" -"@vue/compiler-ssr@3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.0.11.tgz#ac5a05fd1257412fa66079c823d8203b6a889a13" - integrity sha512-66yUGI8SGOpNvOcrQybRIhl2M03PJ+OrDPm78i7tvVln86MHTKhM3ERbALK26F7tXl0RkjX4sZpucCpiKs3MnA== +"@vue/compiler-sfc@^3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.4.tgz#9807868cc950291f163c3930a81bb16e870df097" + integrity sha512-GM+ouDdDzhqgkLmBH4bgq4kiZxJQArSppJiZHWHIx9XRaefHLmc1LBNPmN8ivm4SVfi2i7M2t9k8ZnjsScgzPQ== dependencies: - "@vue/compiler-dom" "3.0.11" - "@vue/shared" "3.0.11" + "@babel/parser" "^7.13.9" + "@babel/types" "^7.13.0" + "@types/estree" "^0.0.48" + "@vue/compiler-core" "3.2.4" + "@vue/compiler-dom" "3.2.4" + "@vue/compiler-ssr" "3.2.4" + "@vue/shared" "3.2.4" + consolidate "^0.16.0" + estree-walker "^2.0.1" + hash-sum "^2.0.0" + lru-cache "^5.1.1" + magic-string "^0.25.7" + merge-source-map "^1.1.0" + postcss "^8.1.10" + postcss-modules "^4.0.0" + postcss-selector-parser "^6.0.4" + source-map "^0.6.1" "@vue/compiler-ssr@3.0.5": version "3.0.5" @@ -1849,18 +1847,19 @@ "@vue/compiler-dom" "3.0.5" "@vue/shared" "3.0.5" +"@vue/compiler-ssr@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.4.tgz#be51f219c2042b3e530373e60bc126ada6bb1cc0" + integrity sha512-bKZuXu9/4XwsFHFWIKQK+5kN7mxIIWmMmT2L4VVek7cvY/vm3p4WTsXYDGZJy0htOTXvM2ifr6sflg012T0hsw== + dependencies: + "@vue/compiler-dom" "3.2.4" + "@vue/shared" "3.2.4" + "@vue/devtools-api@^6.0.0-beta.11": version "6.0.0-beta.11" resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.11.tgz#4fb4161ee41ba75f3f5198d4bfd80e4ffb7f2462" integrity sha512-vpw61AkW9U8c2upjJCljHq9eh1KkD4FJ7DYbRzIETpj9WAw2VESudJZosAk4M+7npBo1Zu1jNQY03HUMVO/czQ== -"@vue/reactivity@3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.0.11.tgz#07b588349fd05626b17f3500cbef7d4bdb4dbd0b" - integrity sha512-SKM3YKxtXHBPMf7yufXeBhCZ4XZDKP9/iXeQSC8bBO3ivBuzAi4aZi0bNoeE2IF2iGfP/AHEt1OU4ARj4ao/Xw== - dependencies: - "@vue/shared" "3.0.11" - "@vue/reactivity@3.0.5": version "3.0.5" resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.0.5.tgz#e3789e4d523d845f9ae0b4d770e2b45594742fd2" @@ -1868,13 +1867,12 @@ dependencies: "@vue/shared" "3.0.5" -"@vue/runtime-core@3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.0.11.tgz#c52dfc6acf3215493623552c1c2919080c562e44" - integrity sha512-87XPNwHfz9JkmOlayBeCCfMh9PT2NBnv795DSbi//C/RaAnc/bGZgECjmkD7oXJ526BZbgk9QZBPdFT8KMxkAg== +"@vue/reactivity@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.4.tgz#a020ad7e50f674219a07764b105b5922e61597ea" + integrity sha512-ljWTR0hr8Tn09hM2tlmWxZzCBPlgGLnq/k8K8X6EcJhtV+C8OzFySnbWqMWataojbrQOocThwsC8awKthSl2uQ== dependencies: - "@vue/reactivity" "3.0.11" - "@vue/shared" "3.0.11" + "@vue/shared" "3.2.4" "@vue/runtime-core@3.0.5": version "3.0.5" @@ -1884,14 +1882,13 @@ "@vue/reactivity" "3.0.5" "@vue/shared" "3.0.5" -"@vue/runtime-dom@3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.0.11.tgz#7a552df21907942721feb6961c418e222a699337" - integrity sha512-jm3FVQESY3y2hKZ2wlkcmFDDyqaPyU3p1IdAX92zTNeCH7I8zZ37PtlE1b9NlCtzV53WjB4TZAYh9yDCMIEumA== +"@vue/runtime-core@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.4.tgz#da5dde3dc1e48df99dd31ea9a972f5c02acdc3f5" + integrity sha512-W6PtEOs8P8jKYPo3JwaMAozZQivxInUleGfNwI2pK1t8ZLZIxn4kAf7p4VF4jJdQB8SZBzpfWdLUc06j7IOmpQ== dependencies: - "@vue/runtime-core" "3.0.11" - "@vue/shared" "3.0.11" - csstype "^2.6.8" + "@vue/reactivity" "3.2.4" + "@vue/shared" "3.2.4" "@vue/runtime-dom@3.0.5": version "3.0.5" @@ -1902,6 +1899,15 @@ "@vue/shared" "3.0.5" csstype "^2.6.8" +"@vue/runtime-dom@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.4.tgz#1025595f2ae99a12fe0e1e6bce8df6761efec24b" + integrity sha512-HcVtLyn2SGwsf6BFPwkvDPDOhOqkOKcfHDpBp5R1coX+qMsOFrY8lJnGXIY+JnxqFjND00E9+u+lq5cs/W7ooA== + dependencies: + "@vue/runtime-core" "3.2.4" + "@vue/shared" "3.2.4" + csstype "^2.6.8" + "@vue/server-renderer@^3.0.5": version "3.0.5" resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.0.5.tgz#1197e7d7b7874e15de284798a3932ec425ffe593" @@ -1910,16 +1916,16 @@ "@vue/compiler-ssr" "3.0.5" "@vue/shared" "3.0.5" -"@vue/shared@3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.0.11.tgz#20d22dd0da7d358bb21c17f9bde8628152642c77" - integrity sha512-b+zB8A2so8eCE0JsxjL24J7vdGl8rzPQ09hZNhystm+KqSbKcAej1A+Hbva1rCMmTTqA+hFnUSDc5kouEo0JzA== - "@vue/shared@3.0.5": version "3.0.5" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.0.5.tgz#c131d88bd6713cc4d93b3bb1372edb1983225ff0" integrity sha512-gYsNoGkWejBxNO6SNRjOh/xKeZ0H0V+TFzaPzODfBjkAIb0aQgBuixC1brandC/CDJy1wYPwSoYrXpvul7m6yw== +"@vue/shared@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.4.tgz#ba2a09527afff27b28d08f921b4a597e9504ca7a" + integrity sha512-j2j1MRmjalVKr3YBTxl/BClSIc8UQ8NnPpLYclxerK65JIowI4O7n8O8lElveEtEoHxy1d7BelPUDI0Q4bumqg== + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -9073,10 +9079,10 @@ vue-eslint-parser@^5.0.0: esquery "^1.0.1" lodash "^4.17.11" -vue-loader@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-16.2.0.tgz#046a53308dd47e58efe20ddec1edec027ce3b46e" - integrity sha512-TitGhqSQ61RJljMmhIGvfWzJ2zk9m1Qug049Ugml6QP3t0e95o0XJjk29roNEiPKJQBEi8Ord5hFuSuELzSp8Q== +vue-loader@^16.5.0: + version "16.5.0" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-16.5.0.tgz#09c4e0712466899e34b99a686524f19165fb2892" + integrity sha512-WXh+7AgFxGTgb5QAkQtFeUcHNIEq3PGVQ8WskY5ZiFbWBkOwcCPRs4w/2tVyTbh2q6TVRlO3xfvIukUtjsu62A== dependencies: chalk "^4.1.0" hash-sum "^2.0.0" @@ -9090,15 +9096,6 @@ vue-style-loader@^4.1.3: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue@^3.0.11: - version "3.0.11" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.11.tgz#c82f9594cbf4dcc869241d4c8dd3e08d9a8f4b5f" - integrity sha512-3/eUi4InQz8MPzruHYSTQPxtM3LdZ1/S/BvaU021zBnZi0laRUyH6pfuE4wtUeLvI8wmUNwj5wrZFvbHUXL9dw== - dependencies: - "@vue/compiler-dom" "3.0.11" - "@vue/runtime-dom" "3.0.11" - "@vue/shared" "3.0.11" - vue@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.5.tgz#de1b82eba24abfe71e0970fc9b8d4b2babdc3fe1" @@ -9108,6 +9105,15 @@ vue@^3.0.5: "@vue/runtime-dom" "3.0.5" "@vue/shared" "3.0.5" +vue@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.4.tgz#d94d88675e41c050d3a722d0848a7063b5e87a60" + integrity sha512-rNCFmoewm8IwmTK0nj3ysKq53iRpNEFKoBJ4inar6tIh7Oj7juubS39RI8UI+VE7x+Cs2z6PBsadtZu7z2qppg== + dependencies: + "@vue/compiler-dom" "3.2.4" + "@vue/runtime-dom" "3.2.4" + "@vue/shared" "3.2.4" + w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"