定义 Vue-Router 路由时访问 Vuex 状态

Accessing Vuex state when defining Vue-Router routes

我有以下 Vuex 商店(main.js):

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

//init store
const store = new Vuex.Store({
    state: {
        globalError: '',
        user: {
            authenticated: false
        }
     },
     mutations: {
         setGlobalError (state, error) {
             state.globalError = error
         }
     }
})

//init app
const app = new Vue({
    router: Router,
    store,
    template: '<app></app>',
    components: { App }
}).$mount('#app')

我还为 Vue Router (routes.js) 定义了以下路由:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

//define routes
const routes = [
    { path: '/home', name: 'Home', component: Home },
    { path: '/login', name: 'Login', component: Login },
    { path: '/secret', name: 'Secret', component: SecretPage, meta: { requiresLogin: true }
]

我正在努力做到这一点,如果 Vuex 存储 user 对象,并且它的 authenticated 属性 设置为 false,则有路由器将用户重定向到登录页面。

我有这个:

Router.beforeEach((to, from, next) => {
    if (to.matched.some(record => record.meta.requiresLogin) && ???) {
        // set Vuex state's globalError, then redirect
        next("/Login")
    } else {
        next()
    }
})

问题是我不知道如何从 beforeEach 函数内部访问 Vuex 存储的 user 对象。

我知道我可以使用 BeforeRouteEnter 在组件内部设置路由器防护逻辑,但这会使每个组件变得混乱。我想改为在路由器级别集中定义它。

按照建议 here,您可以做的是从其所在的文件中导出您的商店,然后将其导入到 routes.js 中。它将类似于以下内容:

你有一个store.js:

import Vuex from 'vuex'

//init store
const store = new Vuex.Store({
    state: {
        globalError: '',
        user: {
            authenticated: false
        }
     },
     mutations: {
         setGlobalError (state, error) {
             state.globalError = error
         }
     }
})

export default store

现在 routes.js,您可以拥有:

import Vue from 'vue'
import VueRouter from 'vue-router'
import store from ./store.js

Vue.use(VueRouter)

//define routes
const routes = [
    { path: '/home', name: 'Home', component: Home },
    { path: '/login', name: 'Login', component: Login },
    { path: '/secret', name: 'Secret', component: SecretPage, meta: { requiresLogin: true }
]

Router.beforeEach((to, from, next) => {
    if (to.matched.some(record => record.meta.requiresLogin) && ???) {
        // You can use store variable here to access globalError or commit mutation 
        next("/Login")
    } else {
        next()
    }
})

main.js中你也可以输入store:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

import store from './store.js'

//init app
const app = new Vue({
    router: Router,
    store,
    template: '<app></app>',
    components: { App }
}).$mount('#app')

我最终将商店从 main.js 移到 store/index.js,并将其导入 router.js 文件:

import store from './store'

//routes

const routes = [
    { path: '/home', name: 'Home', component: Home },
    { path: '/login', name: 'Login', component: Login },
    { path: '/secret', name: 'Secret', component: SecretPage, meta: { requiresLogin: true }
]    

//guard clause
Router.beforeEach((to, from, next) => {
    if (to.matched.some(record => record.meta.requiresLogin) && store.state.user.authenticated == false) {
        store.commit("setGlobalError", "You need to log in before you can perform this action.")
        next("/Login")
    } else {
        next()
    }
})

这就是我想要的。

在 App.vue 中,我将在存储身份验证详细信息的 cookie 上保留观察者。 (显然我会在身份验证后将包含身份验证详细信息的令牌存储为 cookie)

现在只要这个 cookie 变空,我就会将用户路由到 /login 页面。注销会删除 cookie。现在,如果用户在注销后返回,现在由于 cookie 不存在,(这需要用户登录),用户将被路由到登录页面。

将您的位置状态与应用程序状态的其余部分分开进行管理会使此类事情变得比他们可能需要的更难。在处理完 Redux 和 Vuex 中的类似问题后,我开始使用 router 模块在我的 Vuex 商店中 内部 管理我的位置状态。您可能需要考虑使用该方法。

在您的特定情况下,您可以观察 Vuex 存储本身的位置何时发生变化,并发送适当的 "redirect" 操作,如下所示:

dispatch("router/push", {path: "/login"})

作为 Vuex 模块管理位置状态比您想象的要容易。如果您想尝试一下,可以使用我的作为起点:

https://github.com/geekytime/vuex-router

我发现在 router.py 使用守卫 router.beforeEach 时我无法使用该商店,但是通过更改 守卫 router.beforeResolve,然后商店可用。

我还发现,通过在守卫 router.beforeEach 中等待商店的导入,我可以成功使用 router.beforeEach。我在 router.beforeResolve 代码下方提供了一个示例。

因此,为了使我的示例与 OP 的问题相似,以下是它对我的工作方式。我正在使用 vue-router 3.0.2 和 vuex 3.1.0.

import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '@/store';  //or use a full path to ./store 

Vue.use(VueRouter)

//define routes
const routes = [
    { path: '/home', name: 'Home', component: Home },
    { path: '/login', name: 'Login', component: Login },
    { path: '/secret', name: 'Secret', component: SecretPage, meta: { requiresLogin: true }
]

const router = new VueRouter({
   routes  //es6
 })

router.beforeResolve((to, from, next) => {
    const user = store.state.user.user;  //store with namespaced  modules
    if (to.matched.some(record => record.meta.requiresLogin) && user.isLoggedIn) {
       next() //proceed to the route
    } else next("/login")  //redirect to login

})

export default router;

我还发现我可以通过在 beforeEach 守卫中等待加载商店来让 router.beforeEach 工作。

router.beforeEach(async (to, from, next) => {
  const store = await import('@/store');  //await the store 
  const user = store.state.user.user;  //store with namespaced modules
  if (to.matched.some(record => record.meta.requiresLogin) && user.isLoggedIn) {
  ....  //and continue as above
});

按照@Saurabh 的建议导入商店。但是恕我直言,它会给您的代码带来某种变通方法的味道。

它有效,因为 Vuex 存储是单例的。导入它会在您的组件、路由器和商店之间创建一个硬链接依赖关系。至少它使单元测试变得更加困难。 vue-router 解耦并像这样工作是有原因的,遵循其建议的模式并保持路由器与实际商店实例的解耦可能会有所回报。

查看 vue-router 的源代码,很明显有一种更优雅的方式可以从路由器访问商店,例如在 beforeRouteEnter 后卫中:

beforeRouteEnter: (to, from, next) => {
  next(vm => {
    // access any getter/action here via vm.$store
    // avoid importing the store singleton and thus creating hard dependencies
  })
}

2020 年 9 月 10 日编辑(感谢@Andi 指出)

使用beforeRouteEnter守卫则视具体情况而定。蝙蝠我看到以下选项:

  1. mixin 中声明守卫,并在需要它的组件中有选择地使用它,而不是在全局守卫中过滤需要的组件
  2. global mixin中声明守卫(注意声明特性,例如需要在Vue.use(VueRouter);之后声明:here and here

我发现 vuex-router-sync 是最简单的解决方案。来自他们的网站:

It adds a route module into the store, which contains the state representing the current route

您可以使用 router.app 访问路由器注入的根 Vue 实例,然后通过 router.app.$store 定期访问存储。

const router = new Router({
    routes,
})

router.beforeEach((to, from, next) => {
    // access store via `router.app.$store` here.
    if (router.app.$store.getters('user')) next();
    else next({ name: 'login' });
})

这里是 API Reference.

Vue 3

router.app 在 Vue 3 中被删除,但您仍然可以在使用路由器时添加它,如 migration guide:

中所述
app.use(router)
router.app = app