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

Ege*_*soz 26 javascript vue.js vue-router vuex vuejs2

我有以下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')
Run Code Online (Sandbox Code Playgroud)

我还为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 }
]
Run Code Online (Sandbox Code Playgroud)

我试图这样做,如果Vuex商店的user对象具有authenticated属性false,请让路由器将用户重定向到Login页面.

我有这个:

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

问题是我不知道如何user从beforeEach函数中访问Vuex商店的对象.

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

Sau*_*abh 37

正如这里建议的那样,您可以做的是从您所在的文件中导出您的商店并将其导入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
Run Code Online (Sandbox Code Playgroud)

现在在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()
    }
})
Run Code Online (Sandbox Code Playgroud)

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')
Run Code Online (Sandbox Code Playgroud)

  • @vintproykt 这是真的,99.9% 的情况下。我曾经看到过,当 webpack 认为同一模块的两个相对导入路径不相等时,它会创建一个重复的实例。花了很长时间来调试。我用DI解决了它。 (2认同)
  • 对于依赖注入问题来说,这是一个糟糕的解决方案。路由器现在已耦合到存储区。如果路由器是在框架中创建的,而存储不是在应用程序中创建的,该怎么办? (2认同)

Ege*_*soz 8

我最终将 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()
    }
})
Run Code Online (Sandbox Code Playgroud)

  • 如果由于循环依赖问题需要将路由器导入商店,这将导致问题。 (4认同)

Haf*_*ari 8

您可以使用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' });
})
Run Code Online (Sandbox Code Playgroud)

这是API 参考

  • 请注意;至少在我的项目中,“$store”不会在第一页渲染时注入(第一次调用“beforeEach”)。但是,如果您使用“beforeResolve”,则“$store”将在第一次渲染时注入。 (7认同)
  • 对于我来说,仍然在第一页渲染中,商店有时是未定义的...beforeEach 和 beforeResolve 相同..有什么想法吗? (2认同)

Chr*_*nes 6

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

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

dispatch("router/push", {path: "/login"})
Run Code Online (Sandbox Code Playgroud)

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

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


el.*_*cko 6

按照@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
  })
}
Run Code Online (Sandbox Code Playgroud)

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

beforeRouteEnter然后根据具体情况使用防护装置。我立即看到以下选项:

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


Ken*_*ley 5

我发现在使用保护 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;
Run Code Online (Sandbox Code Playgroud)

我还发现我可以通过在 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
});
Run Code Online (Sandbox Code Playgroud)

  • 如果我错了,请纠正我,但这只会等待动态 webpack 导入,而不是实际“加载”商店或将商店绑定到 vue 应用程序对象的 vue 代码,对吗?那么这有点类似于执行 setTimeout(1) 之类的操作来“等待”循环通过? (2认同)
  • @KenBuckley - 这里未经证实的猜测,但我相信原因是您将存储作为变量导入,而不是访问附加到 Vue 对象的存储,从而绕过了所有反应性。我认为它与异步一起工作的原因只是巧合,那时竞争条件已经解决了。 (2认同)