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

IT技术 javascript vue.js vuejs2 vue-router vuex
2021-03-14 06:27:32

我有以下 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()
    }
})

问题是我不知道如何userbeforeEach函数内部访问 Vuex 商店的对象

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

6个回答

正如此处所建议的,您可以做的是从商店所在的文件中导出商店并将其导入到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在上面的例子中访问组件(主页、登录、秘密)。我怎样才能做到这一点?我的问题:stackoverflow.com/questions/49959675/...
2021-04-15 06:27:32
@vintproykt 在 99.9% 的情况下都是这样。我见过 webpack 创建一个重复的实例,当它认为同一module的两个相对导入路径不相等时。花了很长时间来调试。我用 DI 解决了它。
2021-04-17 06:27:32
谢谢Saurabh,这正是我最终要做的。我会选择你的答案,因为它比我的更广泛。:)
2021-04-21 06:27:32
这是依赖注入问题的糟糕解决方案。路由器现在耦合到商店。如果路由器是在框架中创建的,而商店不是在应用程序中创建的,该怎么办?
2021-04-28 06:27:32
每次导入时,这怎么不创建一个新的 Store?
2021-04-29 06:27:32

您可以使用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 参考

视图 3

router.app在Vue公司3去除,但使用路由器时,作为中说明你仍然可以将其添加迁移指南

app.use(router)
router.app = app
只是一个注意事项;至少在我的项目中, $store没有注入第一页渲染(第一次调用beforeEach)。但是,如果您使用beforeResolvethen$store将在第一次渲染时注入。
2021-05-12 06:27:32

我最终将 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()
    }
})
@DanielTwigg 不,不会。循环依赖解析由webpack解决
2021-04-22 06:27:32
如果由于循环依赖问题需要将路由器导入商店,这将导致问题。
2021-05-15 06:27:32

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

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

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

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

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);: herehere之后声明
@el.niko 在他的最后一段中,他说他知道,beforeRouteEnter但他想“集中定义”
2021-04-15 06:27:32
@andi 当然。我很想知道您对这如何成为操作员要求的功能限制的看法?我忽略了什么吗?
2021-04-30 06:27:32
但这是一个In-Component Guard,因此您不能为所有路由全局定义它
2021-05-08 06:27:32
@andi,非常感谢您指出这一点。AFAICS OP 并未将解决方案限制为静态解决方案,但是您的评论是有效的。我似乎已经制定了解决方案的基础工作,但最终没有解释我的动机和 sln 本身。非静态方法的美妙之处在于它可以更灵活地注入,从而更好地兼容非平凡的应用程序(例如多个商店实例等)。我编辑了我的答案。
2021-05-13 06:27:32