查看: 254|回复: 3

Vue2 Vuex状态管理与路由守卫登录权限控制实践

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
在前端项目里,组件通信只能解决父子层级的问题,一旦数据要跨页面共享、路由要按登录状态放行、接口实例要全局复用,就需要把 mixin、插件、Vuex、本地存储和 Vue Router 组合起来用。下面按“复用逻辑—全局能力—状态集中—路由控制”的顺序,梳理一套 Vue 2 项目里可直接落地的实现方式,文中的命令、配置与代码逻辑均来自原文示例。

一、组件通信的边界在哪里
父子组件通信的基础手段是 props 向下传值、$emit 向上通知,父组件需要命令式操作子组件时用 $refs。当数据层级较深、多个页面都要读写同一份数据时,继续靠 props 逐层透传就会失控,这时应交给 Vuex 或 Pinia 集中管理。这是后面所有方案的前提判断:局部数据留在组件内,跨页面共享数据才进 store。

二、用 mixin 抽取重复选项
混入适合把多个组件重复出现的 data、方法和生命周期逻辑提取出来。组件引入混入后,混入对象会与组件自身选项合并,同名冲突时组件自身选项优先,生命周期钩子则依次执行。
  1. // src/mixins/userMixin.js
  2. export default {
  3.   data() {
  4.     return { userName: '访客' }
  5.   },
  6.   methods: {
  7.     resetUserName() {
  8.       this.userName = '访客'
  9.     }
  10.   }
  11. }
  12. // 组件内引入
  13. import userMixin from '@/mixins/userMixin'
  14. export default {
  15.   name: 'ProfileView',
  16.   mixins: [userMixin]
  17. }
复制代码

也可以在 main.js 中通过 Vue.mixin() 注册全局混入,但它会作用到每一个组件,容易带来隐式依赖和命名冲突。除非逻辑足够通用,否则优先用局部混入或组合式工具函数。

三、插件:把全局能力收口到 install
插件的本质通常是带有 install(Vue, options) 方法的对象,调用 Vue.use(plugin) 时 Vue 会自动执行它。常见用途包括注册组件、注册自定义指令、挂载原型方法。下面把 axios 实例封装成插件:
  1. // src/plugins/http.js
  2. import axios from 'axios'
  3. const http = axios.create({
  4.   baseURL: 'http://127.0.0.1:8000',
  5.   timeout: 5000
  6. })
  7. export default {
  8.   install(Vue) {
  9.     // Vue 2 中通过原型提供 this.$http
  10.     Vue.prototype.$http = http
  11.   }
  12. }
  13. // main.js
  14. import Vue from 'vue'
  15. import App from './App.vue'
  16. import httpPlugin from '@/plugins/http'
  17. Vue.use(httpPlugin)
  18. new Vue({
  19.   render: h => h(App)
  20. }).$mount('#app')
复制代码

注册后组件里直接 this.$http.get(...) 即可,无需每个文件重复创建实例。指令同样可以放在插件中注册,例如自动聚焦:
  1. Vue.directive('focus', {
  2.   inserted(element) {
  3.     element.focus()
  4.   }
  5. })
复制代码

Vue Router、Vuex、Element UI 这些第三方库也是通过 Vue.use() 接入的。插件挂到原型上的能力建议统一使用 $ 前缀命名,降低与组件自身 data、methods 撞名的概率。

四、插槽:子组件定布局,父组件填内容
默认插槽与具名插槽让父组件向子组件注入内容,子组件只负责整体结构,标题、正文、按钮等可变部分由父组件提供,适合卡片、弹窗、布局类组件。
  1. <!-- AboutView.vue -->
  2. <template>
  3.   <ContentCard>
  4.     <template slot="header"><h2>关于页面</h2></template>
  5.     <p>这里是父组件传入的正文。</p>
  6.     <template slot="footer"><button>确定</button></template>
  7.   </ContentCard>
  8. </template>
  9. <!-- ContentCard.vue,Vue 2 写法 -->
  10. <template>
  11.   <section class="card">
  12.     <header><slot name="header">默认标题</slot></header>
  13.     <main><slot>默认内容</slot></main>
  14.     <footer><slot name="footer"></slot></footer>
  15.   </section>
  16. </template>
复制代码

五、Vuex:state、getters、mutations、actions 的分工
Vuex 把多组件共享的数据集中放进 store,用 mutations 同步修改状态,用 actions 承载异步逻辑或业务流程。Vue 3 项目也能用 Vuex,但新项目一般优先 Pinia。

创建 store 的写法:
  1. // src/store/index.js
  2. import Vue from 'vue'
  3. import Vuex from 'vuex'
  4. Vue.use(Vuex)
  5. export default new Vuex.Store({
  6.   state: {
  7.     count: 100
  8.   },
  9.   getters: {
  10.     doubleCount(state) {
  11.       return state.count * 2
  12.     }
  13.   },
  14.   mutations: {
  15.     add(state, amount) {
  16.       state.count += amount
  17.     }
  18.   },
  19.   actions: {
  20.     addCount({ state, commit }, amount) {
  21.       if (state.count + amount > 105) return
  22.       commit('add', amount)
  23.     }
  24.   }
  25. })
复制代码

这里的角色划分很明确:state 存共享数据,getters 负责派生计算,mutations 是同步改状态的唯一推荐入口,actions 可以放异步逻辑并通过 commit 提交 mutation。上面的 addCount 演示了在 action 里先做上限校验再提交,避免无效的状态变更。

入口注册 store:
  1. // main.js
  2. import Vue from 'vue'
  3. import App from './App.vue'
  4. import store from './store'
  5. new Vue({
  6.   store,
  7.   render: h => h(App)
  8. }).$mount('#app')
复制代码

组件中读取与派发:
  1. <template>
  2.   <div>
  3.     <p>数量:{{ $store.state.count }}</p>
  4.     <p>两倍数量:{{ $store.getters.doubleCount }}</p>
  5.     <button @click="increase">增加</button>
  6.   </div>
  7. </template>
  8. <script>
  9. export default {
  10.   methods: {
  11.     increase() {
  12.       this.$store.dispatch('addCount', 1)
  13.     }
  14.   }
  15. }
  16. </script>
复制代码

需要强调的是不要在组件里直接改 this.$store.state,绕过 mutation 后状态变化难以追踪。另外小型项目不必把所有变量都塞进 Vuex,只有跨页面或跨组件共享、且需要统一修改规则的状态才值得集中管理。

六、本地存储的封装与 token 取舍
浏览器常见存储有三类:Cookie 可设置过期时间且会随请求发送;sessionStorage 只在当前标签页会话内有效;localStorage 长期保存,除非主动删除或清理站点数据。三者都不适合存放密码等高敏感信息。

localStorage 与 sessionStorage 只能存字符串,数组和对象必须先序列化:
  1. const user = { name: 'lqz', age: 19 }
  2. localStorage.setItem('user', JSON.stringify(user))
  3. const savedUser = JSON.parse(localStorage.getItem('user') || '{}')
  4. sessionStorage.setItem('draft', '临时内容')
  5. sessionStorage.removeItem('draft')
  6. localStorage.removeItem('user')
复制代码

直接 JSON.parse 空值或脏数据会抛异常,因此建议封装一层读写方法统一处理:
  1. export function setStorage(key, value) {
  2.   localStorage.setItem(key, JSON.stringify(value))
  3. }
  4. export function getStorage(key, fallback = null) {
  5.   const value = localStorage.getItem(key)
  6.   if (value === null) return fallback
  7.   try {
  8.     return JSON.parse(value)
  9.   } catch {
  10.     return value
  11.   }
  12. }
复制代码

登录 token 存哪里要结合安全策略决定:localStorage 使用简单,但 XSS 可以读取其中的 token;HttpOnly Cookie 能降低脚本读取风险,但需要配合 CSRF 防护。永久保存不等于安全保存,这一点在选型时常被忽略。

七、Vue Router 安装、配置与跳转参数
Vue Router 负责把 URL 映射到页面组件:router-view 是页面组件的渲染位置,router-link 是声明式导航,this.$router 用于代码跳转,this.$route 表示当前路由信息。

Vue 2 对应 Vue Router 3,安装命令如下:
  1. npm install vue-router@3
复制代码
  1. // src/router/index.js
  2. import Vue from 'vue'
  3. import VueRouter from 'vue-router'
  4. import HomeView from '@/views/HomeView.vue'
  5. import AboutView from '@/views/AboutView.vue'
  6. Vue.use(VueRouter)
  7. export default new VueRouter({
  8.   mode: 'history',
  9.   routes: [
  10.     { path: '/', name: 'home', component: HomeView },
  11.     { path: '/about', name: 'about', component: AboutView }
  12.   ]
  13. })
复制代码
  1. <!-- App.vue -->
  2. <template>
  3.   <div id="app">
  4.     <router-link to="/">首页</router-link>
  5.     <router-link to="/about">关于</router-link>
  6.     <router-view />
  7.   </div>
  8. </template>
复制代码

最后把 router 注入根实例:
  1. import router from './router'
  2. new Vue({
  3.   router,
  4.   render: h => h(App)
  5. }).$mount('#app')
复制代码

传参方面,query 适合筛选、分页这类可选信息,不会改变路由匹配的组件;params 配合路径参数更适合标识某个资源:
  1. // JavaScript 跳转
  2. this.$router.push({
  3.   name: 'about',
  4.   query: { name: '张三', age: 19 }
  5. })
  6. this.$router.push({
  7.   name: 'user',
  8.   params: { id: 10 }
  9. })
  10. // 路由配置:路径参数
  11. { path: '/user/:id', name: 'user', component: UserView }
  12. // UserView.vue 中读取
  13. const id = this.$route.params.id
  14. const name = this.$route.query.name
复制代码

push 会新增浏览历史,replace 替换当前记录,back() 与 go(-1) 返回上一页。需要记住:路由参数来自地址栏,不能直接当作权限依据,敏感操作仍要由后端认证。

八、嵌套路由支撑后台布局
后台管理页面通常共享顶部和侧边栏,只替换右侧内容,这时用嵌套路由。父组件里必须放 router-view,子路由才有渲染位置。
  1. const routes = [
  2.   {
  3.     path: '/backend',
  4.     component: () => import('@/views/BackendView.vue'),
  5.     children: [
  6.       { path: '', component: () => import('@/views/backend/IndexView.vue') },
  7.       { path: 'orders', component: () => import('@/views/backend/OrderView.vue') },
  8.       { path: 'goods', component: () => import('@/views/backend/GoodsView.vue') }
  9.     ]
  10.   }
  11. ]
复制代码
  1. <!-- BackendView.vue -->
  2. <template>
  3.   <div class="backend">
  4.     <aside>
  5.       <router-link to="/backend">首页</router-link>
  6.       <router-link to="/backend/orders">订单</router-link>
  7.       <router-link to="/backend/goods">商品</router-link>
  8.     </aside>
  9.     <main><router-view /></main>
  10.   </div>
  11. </template>
复制代码

子路由的空 path 对应默认页,切换 orders、goods 时只有 main 内的 router-view 内容变化,侧边栏不会重新渲染。

九、路由守卫做登录保护
全局前置守卫在页面跳转前执行,可用来检查 token、角色或权限。它只负责前端导航体验,接口层权限必须由后端再次校验。
  1. router.beforeEach((to, from, next) => {
  2.   const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
  3.   const token = localStorage.getItem('access')
  4.   if (requiresAuth && !token) {
  5.     next({ name: 'login', query: { redirect: to.fullPath } })
  6.   } else if (to.name === 'login' && token) {
  7.     next({ name: 'home' })
  8.   } else {
  9.     next()
  10.   }
  11. })
复制代码

需要保护的路由通过 meta 标记,to.matched 会匹配嵌套路由链上的所有记录,因此父级标记即可覆盖其子路由:
  1. {
  2.   path: '/backend',
  3.   component: BackendView,
  4.   meta: { requiresAuth: true }
  5. }
复制代码

从上面代码可以看到,每个分支都要为 next 给出明确结果:未登录带着 redirect 跳到登录页,已登录再访问登录页则回到首页。登录成功后读取 redirect 查询参数可以回到用户原本要访问的页面,但必须限制跳转为本站路径,否则会形成开放重定向风险。

十、Hash 与 History 模式的选择
Hash 模式用 URL 中 # 之后的内容表示前端路由,例如 /#/about。浏览器不会把 hash 发送给服务器,部署简单、兼容性好,但地址不够简洁。History 模式使用 /about 这样的正常路径,地址美观,但刷新页面时服务器必须把未知路径回退到 index.html,否则直接 404。选模式时前端需求与后端部署配置要一并考虑:没有可配置的 fallback 规则时,Hash 模式更稳妥。

十一、落地检查清单
1. 重复的 data、方法、生命周期逻辑抽到局部 mixin,慎用 Vue.mixin 全局注册。
2. 全局 axios 实例与自定义指令通过插件 install 统一挂载,原型方法加 $ 前缀。
3. 跨页面共享状态进 Vuex,修改只走 mutations,异步与业务校验放 actions。
4. 存储读写统一封装,JSON 解析异常要有兜底,token 存放方式按 XSS、CSRF 与过期策略权衡。
5. 后台布局用嵌套路由,父组件保留 router-view;受保护路由用 meta.requiresAuth 标记。
6. 守卫只解决导航体验,redirect 只允许站内跳转,接口权限交由后端校验。

按这套结构实现一个带登录保护的后台管理页面:登录后进入嵌套路由,刷新仍能恢复 token,退出后所有受保护路由都跳回登录页,就是一次完整的状态管理与权限控制串联练习。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Vue2 Vuex状态管理与路由守卫登录权限控制实践

楼主这个梳理挺有条理的,按“复用逻辑—全局能力—状态集中—路由控制”的顺序展开,比一上来就堆代码更容易理解。前面几个点也踩得比较准:父子通信确实有边界,跨页面共享数据再上 Vuex 比较合理;mixin 的合并规则和生命周期执行顺序也讲清楚了,尤其提醒全局混入容易带来隐式依赖,这点很实用。插件那部分把 axios 实例收口到 install 里,统一挂 this.$http,并建议用 $ 前缀降低撞名概率,也是实际项目里常用的做法。插槽那段用卡片组件举例,把“子组件定布局、父组件填内容”的分工说明白了。 不过看正文到 actions 那里好像被截断了,addCount 后面的内容、以及标题里提到的路由守卫登录权限控制还没展开,有点可惜。如果后续能补上 Vuex 模块化拆分、token 存本地存储后怎么在路由 beforeEach 里判断放行、以及登录后动态跳转或重定向的处理,就更完整了。整体思路是清楚的,适合 Vue2 项目拿来对照落地。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Vue2 Vuex状态管理与路由守卫登录权限控制实践

这篇梳理挺清楚的,从组件通信边界讲到 mixin、插件、插槽和 Vuex,再落到路由守卫和登录权限,顺序很适合项目里按需落地。几个点我比较认同:跨页面共享数据才进 store,局部状态别硬塞 Vuex;全局 mixin 确实要慎用,局部 mixin 更好控;插件把 axios 实例收口到 install、原型方法加 $ 前缀也很实用。Vuex 部分示例里 actions 似乎还没贴完,后面如果继续写登录权限,建议把 mutation 只做同步状态修改、action 里 commit、路由守卫里判断登录态和重定向讲清楚,配合本地存储时注意避免重复跳转或死循环。整体是一篇很实操的整理,期待路由守卫那部分展开。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Vue2 Vuex状态管理与路由守卫登录权限控制实践

楼主这篇梳理得挺有条理的,从组件通信边界讲到 mixin、插件、插槽,再过渡到 Vuex,这个顺序对刚接手 Vue2 老项目的人比较友好。mixin 同名选项合并优先级和全局混入的隐式依赖风险点说得清楚,插件里把 axios 实例收口到 install、统一用 $ 前缀命名这些细节也确实能少踩坑,插槽部分用卡片组件举例也很直观。 不过看到 actions 里的 addCount 那里好像被截断了,标题里提到的路由守卫和登录权限控制还没展开。实战里比较关心的几个点,比如登录态到底放 Vuex 还是 localStorage、全局前置守卫怎么维护白名单、刷新页面后 store 状态丢失怎么恢复、以及接口 401 时怎么和路由跳转配合,这些正是容易出问题的地方,期待楼主把后半部分补完。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-31 08:53 , Processed in 0.028509 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部