From bfb7415779927d864047e87d30bb53e64b9c8cd3 Mon Sep 17 00:00:00 2001 From: Nathan Koterba Date: Tue, 15 Aug 2017 09:00:26 -0400 Subject: [PATCH] FEATURE: Provide a way to un-sync from router and store (#64) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, vuex-router-sync only publicly exposes a single method: ‘sync’. This method creates a store.watch as well as a router.beforeHook. No publicly available method existed to effectively ‘unsync’ vuex-router-sync and remove the watch and hook, which created ‘dangling’ refrences and a potential memory leak. Users of this library may need an ‘unsync’ function if they, for example, only use Vue.js in specific portions of their webapp (e.g. with React, Angular, etc.) and when users navigate away from that portion they want to remove/clean-up all Vue.js resources and components. Includes a unit test. Had to bump vue-router to version 2.5.0 which was the first version to allow de-registering hook functions. --- README.md | 7 +++++-- index.js | 19 +++++++++++++++++-- package.json | 2 +- test/test.js | 37 +++++++++++++++++++++++++++++++++++-- 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1e58c8b..c37ffd2 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,13 @@ import { sync } from 'vuex-router-sync' import store from './vuex/store' // vuex store instance import router from './router' // vue-router instance -sync(store, router) // done. +const unsync = sync(store, router) // done. Returns an unsync callback fn // bootstrap your app... + +// During app/Vue teardown (e.g., you only use Vue.js in a portion of your app and you +// navigate away from that portion and want to release/destroy Vue components/resources) +unsync() // Unsyncs store from router ``` You can optionally set a custom vuex module name: @@ -28,7 +32,6 @@ You can optionally set a custom vuex module name: sync(store, router, { moduleName: 'RouteModule' } ) ``` - ### How does it work? - It adds a `route` module into the store, which contains the state representing the current route: diff --git a/index.js b/index.js index 64ddade..167520a 100644 --- a/index.js +++ b/index.js @@ -15,7 +15,7 @@ exports.sync = function (store, router, options) { var currentPath // sync router on store change - store.watch( + const storeUnwatch = store.watch( function (state) { return state[moduleName] }, function (route) { if (route.fullPath === currentPath) { @@ -32,7 +32,7 @@ exports.sync = function (store, router, options) { ) // sync store on router navigation - router.afterEach(function (to, from) { + const afterEachUnHook = router.afterEach(function (to, from) { if (isTimeTraveling) { isTimeTraveling = false return @@ -40,6 +40,21 @@ exports.sync = function (store, router, options) { currentPath = to.fullPath store.commit(moduleName + '/ROUTE_CHANGED', { to: to, from: from }) }) + + return function unsync() { + // On unsync, remove router hook + if (afterEachUnHook != null) { + afterEachUnHook() + } + + // On unsync, remove store watch + if (storeUnwatch != null) { + storeUnwatch(); + } + + // On unsync, unregister Module with store + store.unregisterModule(moduleName) + } } function cloneRoute (to, from) { diff --git a/package.json b/package.json index 0ba7cdc..935f1a1 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "vuex": "^2.1.0" }, "peerDependencies": { - "vue-router": "^2.0.0", + "vue-router": "^2.5.0", "vuex": "^2.1.0" } } diff --git a/test/test.js b/test/test.js index f4c80b8..f71fbf9 100644 --- a/test/test.js +++ b/test/test.js @@ -32,7 +32,7 @@ const run = (originalModuleName, done) => { ] }) - sync(store, router, { + const unsync = sync(store, router, { moduleName: originalModuleName }) @@ -56,7 +56,7 @@ const run = (originalModuleName, done) => { Vue.nextTick(() => { expect(app.$el.textContent).toBe('/c/d?n=1#hello c d') - done() + done(unsync) }) } @@ -67,3 +67,36 @@ test('default usage', done => { test('with custom moduleName', done => { run('moduleName', done) }) + +test('desync', done => { + const store = new Vuex.Store() + spyOn(store, "watch").and.callThrough() + + const router = new VueRouter() + + const moduleName = 'testDesync' + const unsync = sync(store, router, { + moduleName: moduleName + }) + + expect(unsync).toBeInstanceOf(Function) + + // Test module registered, store watched, router hooked + expect(store.state[moduleName]).toBeDefined() + expect(store.watch).toHaveBeenCalled() + expect(store._watcherVM).toBeDefined() + expect(store._watcherVM._watchers).toBeDefined() + expect(store._watcherVM._watchers.length).toBe(1) + expect(router.afterHooks).toBeDefined() + expect(router.afterHooks.length).toBe(1) + + // Now unsync vuex-router-sync + unsync() + + // Ensure router unhooked, store-unwatched, module unregistered + expect(router.afterHooks.length).toBe(0) + expect(store._watcherVm).toBeUndefined() + expect(store.state[moduleName]).toBeUndefined() + + done() +}) \ No newline at end of file