【问题标题】:Accessing Vuex state when defining Vue-Router routes定义 Vue-Router 路由时访问 Vuex 状态
【发布时间】:2017-07-25 00:59:04
【问题描述】:

我有以下 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 在组件内部设置路由器保护逻辑,但这会使每个组件变得混乱。我想在路由器级别集中定义它。

【问题讨论】:

    标签: javascript vue.js vuejs2 vue-router vuex


    【解决方案1】:

    正如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()
        }
    })
    

    ma​​in.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')
    

    【讨论】:

    • 感谢 Saurabh,这正是我最终所做的。我会选择你的答案,因为它比我的更广泛。 :)
    • 您好,我想在上面的示例中访问组件(Home、Login、Secret)中的store。我怎样才能做到这一点?我的问题:stackoverflow.com/questions/49959675/…
    • 每次导入时怎么不创建一个新的Store?
    • @vintproykt 这是真的,99.9% 的时间。当 webpack 认为同一个模块的两个相对导入路径不相等时,我曾经看到过创建一个重复实例。花了很长时间来调试。我用 DI 解决了这个问题。
    • 这是一个糟糕的解决依赖注入问题的方法。路由器现在与商店耦合。如果路由器是在框架中创建的,而商店不是在应用程序中创建的,该怎么办?
    【解决方案2】:

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

    【讨论】:

    • 如果由于循环依赖问题需要将路由器导入到store中会出现问题。
    • @DanielTwigg 不,不会。循环依赖解析由 webpack 解决
    【解决方案3】:

    这就是我想要的。

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

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

    【讨论】:

    • 观察者的有趣用法 - 我不知道你可以看 cookie。但问题要求它只能从 /some/ pages 工作。如果用户在公共页面上,您的观察者功能还需要检查该元数据并且不重定向。
    【解决方案4】:

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

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

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

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

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

    【讨论】:

      【解决方案5】:

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

      我还发现,通过在守卫 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
      });
      

      【讨论】:

      • await商店的方法非常好。我用它来解决这样的问题,效果很好!
      • 如果我错了,请纠正我,但这只会等待动态 webpack 导入,而不是商店实际“加载”或将商店绑定到 vue 应用程序对象的 vue 代码,对吗?所以这有点类似于执行 setTimeout(1) 之类的操作来“等待”循环通过?
      • @CalebJay 感谢您对 Caleb 的观察。您是否知道为什么即使是简单的 await 也会将 beforeEach 功能更改为按预期工作的位置,而不是默认位置而不按预期工作?我很想了解原因。
      【解决方案6】:

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

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

      查看 vue-router 的源代码,很明显有一种更优雅的方式可以从路由器访问 store,例如在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. 全局 mixin 中声明守卫(注意声明的特殊性,例如需要在 Vue.use(VueRouter); 之后声明:herehere

      【讨论】:

      • 但这是一个In-Component Guard,所以你不能为所有路由全局定义它
      • @andi 当然。我很想看看您对这可能是操作要求的功能限制的看法?我忽略了什么吗?
      • @el.niko 在他的最后一段中,他说他知道beforeRouteEnter,但他想“集中定义它”
      • @andi,非常感谢您指出这一点。 AFAICS OP 并未将解决方案限制为静态解决方案,但是您的评论是有效的。我似乎已经制定了解决方案的基础工作,但未能将我的动机和 sln 本身解释到底。非静态方法的美妙之处在于它可以更灵活地注入,因此可以更好地兼容非平凡的应用程序(例如多个存储实例等)。我编辑了我的答案。
      【解决方案7】:

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

      它将一个路由模块添加到存储中,其中包含表示当前路由的状态

      【讨论】:

      • 模棱两可的反应。说明您是如何实际实施来解决问题的。
      【解决方案8】:

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

      【讨论】:

      • 请注意;至少在我的项目中,$store 不会在第一页渲染中注入(第一次调用beforeEach)。但是,如果您使用beforeResolve,那么$store 将在第一次渲染时注入。
      猜你喜欢
      • 1970-01-01
      • 2023-03-06
      • 2019-11-11
      • 2021-05-21
      • 1970-01-01
      • 2017-10-02
      • 2023-03-20
      • 2021-04-26
      • 2019-02-24
      相关资源
      最近更新 更多