繁体   English   中英

定义Vue-Router路由时访问Vuex state

[英]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 object,并且它的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 function 内部访问 Vuex 商店的user object。

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

正如此处所建议的,您可以做的是从商店所在的文件中导出商店并将其导入到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')

我最终将 store 从 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()
    }
})

您可以使用router.app访问路由器注入的根 Vue 实例,然后通过router.app.$store定期访问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 参考

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

在您的特定情况下,您可以观察 Vuex 存储本身内的位置何时发生变化,并分派适当的“重定向”操作,如下所示:

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

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

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

按照@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. 全局混入中声明守卫(注意声明特性,例如需要在Vue.use(VueRouter);之后声明:这里这里

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

我还发现,通过在守卫 router.beforeEach 中等待 store 的导入,我能够成功地使用 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
});

这就是我想要的方式。

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

现在,每当此 cookie 变空时,我都会将用户路由到 /login 页面。 注销会删除 cookie。 现在,如果用户在注销后回击,现在由于 cookie 不存在(需要用户登录),用户将被路由到登录页面。

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

它在 store 中添加了一个路由模块,其中包含表示当前路由的状态

我通过使用vuex-persistedstate解决了这个问题。 这会将 vuex 状态保存在您的本地存储中。

在您的商店/index.js 中:

import createPersistedState from "vuex-persistedstate";

const store = new Vuex.Store({
  plugins: [createPersistedState()],
  state: {
     user: null,
     // ...
  },
  mutations: {// ...},
  actions: {// ...},
  getters: {// ...}
});

在 main.js 中你可以这样做:

router.$store = store

然后在路由器下的 index.js 中:

await router.$store.dispatch("abcd")

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM