当前位置: 首页 > news >正文

Vue入门到实战(day7):Vuex 与 Vue Router 深度解析,从原理到实战的前端状态与路由管理(附代码案例)

        在现代前端开发中,构建复杂单页应用(SPA)离不开高效的状态管理和灵活的路由控制。Vue 生态中的 Vuex 和 Vue Router Router 恰好解决了这两大核心问题。本文将从基础概念出发,通过实例代码详解 Vuex 的状态管理机制和 Vue Router 的路由控制方案,帮助开发者系统性掌握这两个工具的使用技巧。

一、Vuex:Vue 应用的状态管理中心

1.1 什么是 Vuex?

Vuex 是 Vue 官方提供的集中式状态管理模式,它采用单一状态树(Single Source of Truth)思想,将应用中所有组件需要共享的数据集中存储在一个全局的 "仓库(Store)" 中,并提供了一套严格的规则保证状态的变更可预测。

简单来说,Vuex 就是应用中的 "全局数据银行",任何组件都能按照规范的方式存取数据,彻底解决了多层嵌套组件通信、跨组件数据共享的难题。

1.2 为什么需要 Vuex?

在小型应用中,组件间的数据传递可以通过props(父传子)和$emit(子传父)实现,但当应用规模扩大,会面临以下问题:

  • 多个组件依赖同一数据(如用户信息、购物车商品)
  • 多个组件需要修改同一数据(如多组件操作同一表单)
  • 非父子组件(如兄弟组件、跨级组件)通信复杂

此时 Vuex 的优势就会凸显:

  • 集中管理共享数据,避免数据流转混乱
  • 提供清晰的状态变更路径,便于调试和维护
  • 支持时间旅行调试(通过 Vue Devtools 追踪状态变化)

1.3 搭建 Vuex 环境(实战步骤)

步骤 1:创建 Store 核心文件

在项目中新建src/store/index.js,作为 Vuex 的配置中心:

// 引入Vue核心库
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 应用Vuex插件(必须在创建store前执行)
Vue.use(Vuex)// 1. 准备actions:响应组件中的用户动作(处理业务逻辑、异步操作)
const actions = {}// 2. 准备mutations:唯一修改state的地方(必须同步操作)
const mutations = {}// 3. 准备state:存储所有共享数据
const state = {}// 4. 创建并暴露store实例
export default new Vuex.Store({actions,mutations,state
})
步骤 2:在入口文件中注入 Store

修改main.js,将 store 挂载到 Vue 实例,使所有组件可访问:

import Vue from 'vue'
import App from './App.vue'
// 引入store
import store from './store'new Vue({el: '#app',render: h => h(App),store, // 注入storebeforeCreate() {Vue.prototype.$bus = this // 全局事件总线(可选,用于简单通信)}
})

1.4 Vuex 核心工作流程与基本使用

Vuex 严格遵循单向数据流原则,数据流转路径为:组件触发动作 → actions 处理 → mutations 修改 → state 更新 → 组件重新渲染

实战案例:实现计数器功能
// src/store/index.js
const actions = {// 响应组件中的"加"操作(可处理业务逻辑)jia(context, value) {// context是一个迷你store,包含commit、dispatch、state等方法// 可在此处添加业务逻辑(如判断、日志记录等)context.commit('JIA', value) // 调用mutations中的方法},// 处理异步操作(如延迟加)jiaAsync(context, value) {setTimeout(() => {context.commit('JIA', value) // 1秒后提交mutation}, 1000)}
}const mutations = {// 真正修改state的地方(必须同步执行)JIA(state, value) {state.sum += value // 直接修改state中的数据}
}// 初始化共享数据
const state = {sum: 0 // 初始值为0
}export default new Vuex.Store({actions,mutations,state
})
组件中操作 Vuex 数据:
<template><div class="counter"><h3>当前和:{{ $store.state.sum }}</h3><div class="buttons"><!-- 直接调用mutation(无业务逻辑时) --><button @click="$store.commit('JIA', 1)">+1</button><!-- 调用action处理业务逻辑 --><button @click="$store.dispatch('jia', 2)">+2</button><!-- 调用异步action --><button @click="$store.dispatch('jiaAsync', 3)">延迟+3</button></div></div>
</template><style scoped>
.buttons {margin-top: 20px;display: flex;gap: 10px;
}
button {padding: 5px 10px;cursor: pointer;
}
</style>

关键原则

  • 组件中不能直接修改 state(如this.$store.state.sum++是禁止的)
  • 必须通过 mutations 修改 state,确保状态变更可追踪
  • 异步操作(如接口请求)必须放在 actions 中,再通过 commit 调用 mutations

1.5 Getters:状态的计算属性

当 state 中的数据需要加工后再使用时,可使用 getters(类似组件的计算属性,会缓存结果)。

// src/store/index.js
const getters = {// 计算sum的10倍bigSum(state) {return state.sum * 10},// 带条件的计算(如sum大于10时显示"较大")sumDesc(state) {return state.sum > 10 ? '数值较大' : '数值较小'}
}export default new Vuex.Store({// ...其他配置getters // 添加getters配置
})

组件中访问 getters:

<template><div><p>当前和的10倍:{{ $store.getters.bigSum }}</p><p>描述:{{ $store.getters.sumDesc }}</p></div>
</template>

1.6 四个 map 方法:简化组件代码

        Vuex 提供了mapStatemapGettersmapActionsmapMutations四个辅助函数,用于简化组件中对 Vuex 的操作。

1. mapState:映射 state 到计算属性
<script>
import { mapState } from 'vuex'export default {computed: {// 方式1:对象写法(组件属性名与state属性名不同时)...mapState({ mySum: 'sum', mySchool: 'school',mySubject: 'subject'}),// 方式2:数组写法(组件属性名与state属性名相同时)...mapState(['sum', 'school', 'subject'])}
}
</script>

使用时直接访问映射后的属性:

<template><div><p>{{ mySum }}</p><p>{{ sum }}</p></div>
</template>
2. mapGetters:映射 getters 到计算属性
<script>
import { mapGetters } from 'vuex'export default {computed: {// 对象写法...mapGetters({ myBigSum: 'bigSum' }),// 数组写法...mapGetters(['bigSum', 'sumDesc'])}
}
</script>
3. mapActions:生成与 actions 交互的方法
<script>
import { mapActions } from 'vuex'export default {methods: {// 对象写法(组件方法名映射到actions中的方法)...mapActions({ add: 'jia', addAsync: 'jiaAsync' }),// 数组写法(方法名需与actions中一致)...mapActions(['jia', 'jiaAsync'])}
}
</script>
4. mapMutations:生成与 mutations 交互的方法
<script>
import { mapMutations } from 'vuex'export default {methods: {// 对象写法...mapMutations({ increment: 'JIA' }),// 数组写法...mapMutations(['JIA'])}
}
</script>

使用注意:调用 mapActions 和 mapMutations 生成的方法时,若需要传递参数,需在模板中绑定事件时传入:

<button @click="add(5)">+5</button> <!-- 传递参数5 -->
<button @click="JIA(10)">+10</button>

1.7 模块化与命名空间:大型项目的必备方案

        当应用规模扩大,共享数据增多时,单一的 store 会变得臃肿。Vuex 的模块化功能可将 store 拆分为多个子模块,每个模块拥有独立的 state、mutations、actions、getters。

实现步骤:
  1. 拆分模块并开启命名空间
// src/store/index.js
// 计数器相关模块
const countAbout = {namespaced: true, // 开启命名空间,避免模块间命名冲突state: { sum: 0 },mutations: {JIA(state, value) {state.sum += value}},actions: {jiaAsync(context, value) {setTimeout(() => {context.commit('JIA', value)}, 1000)}},getters: {bigSum(state) {return state.sum * 10}}
}// 人员管理模块
const personAbout = {namespaced: true,state: {list: [{ id: '001', name: '张三' }]},mutations: {ADD_PERSON(state, person) {state.list.unshift(person) // 添加到数组开头}},actions: {addPersonWang(context, person) {if (person.name.indexOf('王') === 0) {context.commit('ADD_PERSON', person)} else {alert('只能添加姓王的人')}}}
}// 组合模块
export default new Vuex.Store({modules: {countAbout, // 注册count模块personAbout // 注册person模块}
})
  1. 组件中访问模块化数据
<template><div><!-- 1. 访问state --><p>count模块sum:{{ $store.state.countAbout.sum }}</p><p>person模块列表:{{ $store.state.personAbout.list[0].name }}</p><!-- 2. 访问getters --><p>count模块bigSum:{{ $store.getters['countAbout/bigSum'] }}</p><!-- 3. 调用actions --><button @click="$store.dispatch('countAbout/jiaAsync', 1)">+1(异步)</button><button @click="$store.dispatch('personAbout/addPersonWang', {id:'002',name:'王五'})">添加王五</button><!-- 4. 调用mutations --><button @click="$store.commit('countAbout/JIA', 1)">+1</button><button @click="$store.commit('personAbout/ADD_PERSON', {id:'003',name:'赵六'})">添加赵六</button></div>
</template>
  1. 结合 map 方法访问模块化数据
<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'export default {computed: {// 映射count模块的state...mapState('countAbout', ['sum']),// 映射person模块的state...mapState('personAbout', ['list']),// 映射count模块的getters...mapGetters('countAbout', ['bigSum'])},methods: {// 映射count模块的actions...mapActions('countAbout', ['jiaAsync']),// 映射person模块的actions...mapActions('personAbout', ['addPersonWang']),// 映射count模块的mutations...mapMutations('countAbout', ['JIA']),// 映射person模块的mutations...mapMutations('personAbout', ['ADD_PERSON'])}
}
</script>

二、Vue Router:Vue 应用的路由管理系统

2.1 路由的基本概念

  • 路由(Route):路径与组件的映射关系(key-value)
  • 路由器(Router):管理多个路由的容器,负责路径匹配和组件切换
  • 前端路由:基于 URL 路径实现组件切换,无需刷新页面,是 SPA 的核心特性

前端路由的实现原理:

  • 监听 URL 变化(如hashchange事件或history API)
  • 根据 URL 匹配对应的组件
  • 渲染匹配到的组件(替换页面中的指定区域)

2.2 基本使用步骤(实战)

步骤 1:安装 vue-router

根据 Vue 版本选择对应的 vue-router 版本:

# Vue2项目安装3.x版本
npm i vue-router@3
# Vue3项目安装4.x版本
npm i vue-router@4
步骤 2:创建路由配置文件

新建src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
// 引入路由组件(建议放在pages文件夹,区分于普通组件)
import About from '../pages/About'
import Home from '../pages/Home'// 应用路由插件
Vue.use(VueRouter)// 定义路由规则
const routes = [{path: '/about', // 访问路径component: About // 对应组件},{path: '/home',component: Home}
]// 创建路由实例
const router = new VueRouter({routes // 配置路由规则(ES6属性简写)
})// 暴露路由实例
export default router
步骤 3:在入口文件中注入路由

修改main.js

import Vue from 'vue'
import App from './App.vue'
// 引入路由实例
import router from './router'new Vue({el: '#app',render: h => h(App),router // 注入路由,使所有组件可访问$router和$route
})
步骤 4:使用路由实现导航与组件展示
<template><div id="app"><!-- 路由导航:使用router-link代替a标签 --><div class="nav"><router-link to="/about" active-class="active">关于我们</router-link><router-link to="/home" active-class="active">首页</router-link></div><!-- 组件展示区域:匹配的组件将在这里渲染 --><router-view></router-view></div>
</template><style>
.nav {margin: 20px;
}
/* 路由激活状态样式 */
.active {color: #42b983;text-decoration: none;margin: 0 10px;font-weight: bold;
}
/* 去除默认下划线 */
router-link {text-decoration: none;color: #333;margin: 0 10px;
}
</style>

2.3 路由使用的注意事项

  1. 组件分类与存放

    • 路由组件:与路由匹配的组件(如 About、Home),建议放在pages文件夹
    • 普通组件:被路由组件引用的组件(如 Button、Card),建议放在components文件夹
  2. 路由组件的生命周期

    • 路由切换时,未显示的路由组件会被销毁(触发beforeDestroydestroyed
    • 再次访问时,路由组件会重新挂载(触发beforeCreatecreatedmounted
  3. 路由相关属性

    • $route:当前路由信息对象(包含路径、参数、查询等),每个组件独有
    • $router:全局路由实例对象(包含跳转方法),整个应用只有一个

2.4 多级路由(嵌套路由)

实际应用中经常需要多级路由(如/home/news/home/message),配置方式如下:

// src/router/index.js
import News from '../pages/News'
import Message from '../pages/Message'const routes = [{path: '/home',component: Home,// 子路由配置(children是数组,内部放路由规则)children: [{path: 'news', // 注意:子路由路径不要加斜杠/component: News},{path: 'message', // 完整路径为/home/messagecomponent: Message}]}
]

在父组件中添加子路由导航:

<!-- Home组件 -->
<template><div><h2>首页</h2><div><router-link to="/home/news">新闻</router-link><router-link to="/home/message">消息</router-link></div><router-view></router-view> <!-- 子组件将在这里渲染 --></div>
</template>

关键原则:子路由路径不要加斜杠,跳转时需写完整路径(如/home/news)。

2.5 路由参数传递

路由参数是组件间传递数据的重要方式,Vue Router 支持两种参数传递方式:query 参数和 params 参数。

1. query 参数(URL 参数)
  • 特点:参数显式在 URL 中(如/detail?id=1&title=消息),类似 GET 请求参数
  • 适用场景:非敏感数据传递,支持刷新页面保留参数

传递方式

<!-- 方式1:字符串写法 -->
<router-link to="/home/message/detail?id=1&title=第一条消息">查看详情</router-link><!-- 方式2:对象写法(更灵活) -->
<router-link :to="{path: '/home/message/detail', // 路径query: { // 参数对象id: 1,title: '第一条消息'}}"
>查看详情</router-link>

接收参数

<!-- Detail组件 -->
<template><div><p>消息ID:{{ $route.query.id }}</p><p>消息标题:{{ $route.query.title }}</p></div>
</template>
2. params 参数(路径参数)
  • 特点:参数隐藏在路径中(如/detail/1/第一条消息),类似 RESTful 风格
  • 适用场景:需要参数作为路径一部分的场景(如详情页 ID)

传递方式

  1. 先在路由规则中声明 params 参数(使用占位符):
{name: 'xiangqing', // 建议给路由命名path: 'detail/:id/:title', // 用:参数名声明params参数component: Detail
}
  1. 传递参数:
<!-- 方式1:字符串写法 -->
<router-link to="/home/message/detail/1/第一条消息">查看详情</router-link><!-- 方式2:对象写法(必须用name,不能用path) -->
<router-link :to="{name: 'xiangqing', // 使用路由名称(必须)params: { // 参数对象id: 1,title: '第一条消息'}}"
>查看详情</router-link>

接收参数

<template><div><p>消息ID:{{ $route.params.id }}</p><p>消息标题:{{ $route.params.title }}</p></div>
</template>

重要区别:params 参数在路由规则中声明后,路径必须包含这些参数,否则会匹配失败;而 query 参数是可选的。

2.6 命名路由:简化路由跳转

给路由设置name属性,可以简化跳转路径的编写,尤其适合多级路由。

// 路由规则中添加name属性
{path: '/home/message/detail',name: 'xiangqing', // 路由名称component: Detail,props: true // 开启props接收参数
}

使用命名路由跳转:

<!-- 简化前:需写完整路径 -->
<router-link to="/home/message/detail">跳转</router-link><!-- 简化后:直接使用name -->
<router-link :to="{name: 'xiangqing'}">跳转</router-link><!-- 配合参数传递 -->
<router-link :to="{name: 'xiangqing',query: { id: 1 },params: { title: '消息' }}"
>跳转</router-link>

2.7 路由的 props 配置:优雅接收参数

通过 props 配置,可以让路由组件更方便地接收参数,避免在组件中直接使用$route(降低耦合)。

{name: 'xiangqing',path: 'detail/:id',component: Detail,// 方式1:对象形式(传递固定值)// props: { a: 100, b: '固定值' }// 方式2:布尔值(自动将params参数转为props)// props: true// 方式3:函数形式(灵活处理参数,支持query和params)props($route) {return {id: $route.params.id,title: $route.query.title,extra: '附加信息' // 可添加额外参数}}
}

组件中通过 props 接收:

<script>
export default {props: ['id', 'title', 'extra'], // 直接声明接收的参数mounted() {console.log(this.id, this.title, this.extra) // 使用参数}
}
</script>

2.8 编程式路由导航:通过代码控制跳转

        除了使用<router-link>,还可以通过 JS 代码实现路由跳转,更灵活地控制跳转时机(如点击按钮后验证通过才跳转)。

<template><div><button @click="goDetail">跳转到详情页</button><button @click="goBack">后退</button><button @click="goForward">前进</button></div>
</template><script>
export default {methods: {goDetail() {// 1. push跳转:新增历史记录(可回退)this.$router.push({name: 'xiangqing',params: { id: 1 },query: { title: '消息' }})// 2. replace跳转:替换当前历史记录(不可回退到当前页)this.$router.replace({name: 'xiangqing',params: { id: 1 }})},goBack() {this.$router.back() // 后退一步},goForward() {this.$router.forward() // 前进一步},go(n) {this.$router.go(n) // 前进n步(n为负数则后退,如go(-2)后退两步)}}
}
</script>

2.9 缓存路由组件:保持组件状态

默认情况下,路由切换时组件会被销毁。使用<keep-alive>可以缓存路由组件,使其保持挂载状态(如保留表单输入内容)。

<!-- 缓存指定组件(通过组件名) -->
<keep-alive include="News"><router-view></router-view>
</keep-alive><!-- 缓存多个组件(数组形式) -->
<keep-alive :include="['News', 'Message']"><router-view></router-view>
</keep-alive><!-- 缓存所有组件 -->
<keep-alive><router-view></router-view>
</keep-alive>

注意:被缓存的组件会触发两个特殊的生命周期钩子:

  • activated:组件被激活(显示)时触发
  • deactivated:组件失活(隐藏)时触发

2.10 路由守卫:控制路由访问权限

路由守卫用于在路由跳转过程中进行拦截和控制(如权限验证、登录判断),分为三类:全局守卫、独享守卫、组件内守卫。

1. 全局守卫

作用于所有路由,在src/router/index.js中配置:

// 全局前置守卫:初始化时执行,每次路由切换前执行
router.beforeEach((to, from, next) => {console.log('全局前置守卫', to, from)// 判断是否需要权限验证(通过路由元信息meta)if (to.meta.isAuth) {// 权限验证逻辑(如判断本地存储中的token)if (localStorage.getItem('token')) {next() // 验证通过,放行} else {alert('请先登录')// next('/login') // 可跳转到登录页}} else {next() // 无需验证,直接放行}
})// 全局后置守卫:初始化时执行,每次路由切换后执行
router.afterEach((to, from) => {console.log('全局后置守卫', to, from)// 修改页面标题(通过路由元信息)document.title = to.meta.title || '默认标题'
})

在路由规则中添加元信息meta

{path: '/about',component: About,meta: { isAuth: true, // 是否需要权限验证title: '关于我们' // 页面标题}
}
2. 独享守卫

只作用于单个路由,在路由规则中配置:

{path: '/home/news',component: News,meta: { isAuth: true },// 独享守卫(只有前置,无后置)beforeEnter(to, from, next) {console.log('独享守卫', to, from)if (to.meta.isAuth) {if (localStorage.getItem('token')) {next()} else {alert('无权限访问')}} else {next()}}
}
3. 组件内守卫

在路由组件内部定义,作用于当前组件:

<script>
export default {// 进入守卫:通过路由规则进入组件时触发beforeRouteEnter(to, from, next) {console.log('进入组件', to, from)if (to.meta.isAuth) {if (localStorage.getItem('token')) {next()} else {next(false) // 阻止进入}} else {next()}},// 离开守卫:通过路由规则离开组件时触发beforeRouteLeave(to, from, next) {console.log('离开组件', to, from)const confirm = window.confirm('确定要离开吗?未保存的数据会丢失')if (confirm) {next() // 确认离开} else {next(false) // 取消离开}}
}
</script>

2.11 路由器的两种工作模式

Vue Router 支持两种 URL 模式:hash模式和history模式,可在创建路由实例时配置。

1. hash 模式(默认)
  • URL 格式:http://localhost:8080/#/home(包含 #号)
  • 原理:通过hashchange事件监听 URL 中 #后的变化
  • 特点:
    • #后的内容不会发送到服务器,兼容性好(支持所有浏览器)
    • URL 中带有 #号,不够美观
    • 部分第三方平台分享时可能识别为不合法 URL
2. history 模式
  • URL 格式:http://localhost:8080/home(无 #号)
  • 原理:使用 HTML5 的history API(pushStatereplaceState
  • 特点:
    • URL 干净美观,符合常规 URL 规范
    • 兼容性稍差(IE10 + 支持)
    • 应用部署时需要后端支持(避免刷新页面出现 404)

配置方式

const router = new VueRouter({mode: 'history', // 默认为hashroutes
})

部署注意:history 模式需要后端配合,在服务器端配置所有路由指向 index.html(如 Nginx 的 try_files 配置),否则刷新页面会出现 404 错误。

三、总结

Vuex 和 Vue Router 是 Vue 生态中构建复杂应用的必备工具:

  • Vuex通过集中式存储管理应用状态,解决了跨组件数据共享的难题。其核心是state(数据存储)、mutations(同步修改)、actions(异步处理)、getters(数据加工),配合模块化可应对大型项目的状态管理需求。

  • Vue Router实现了单页应用的路由控制,支持多级路由、参数传递、编程式导航、路由守卫等功能。通过<router-link><router-view>实现组件的按需加载与切换,让 SPA 开发更加高效。

        掌握这两个工具的使用,不仅能提升开发效率,更能让代码结构更清晰、可维护性更强。建议在实际项目中多练习,结合 Vue Devtools 调试工具深入理解其工作原理。

http://www.dtcms.com/a/617619.html

相关文章:

  • 3种数据模型的演变
  • Highcharts常见问题解析(5):可以在服务器上使用 Highcharts 吗?如何正确集成?
  • 用 Node.js 手动搭建 HTTP 服务器:从零开始的 Web 开发之旅!
  • 前端使用 React Query 管理“服务器状态”
  • 佛山cms建站帮人做兼职的网站
  • Spring Boot的web基础配置
  • 下载网站上的表格 怎么做天津市建设工程质量安全监督管理总队网站
  • 【Linux日新月异(八)】CentOS 7系统资源监控与排查深度指南:性能调优全景图
  • word中怎么查看插入的图片占用内存大小来压缩word文件整体大小
  • Flink CDC + MaxCompute用 MaxCompute Connector 打通实时入湖通道
  • 【AI 学习】AI Agent 开发进阶:架构、规划、记忆与工具编排
  • 二十三、Transformer架构详解
  • JAR逆向工程实战对比:传统工具 vs 自动化解决方案
  • 算法学习--离散化
  • 沈阳住房和城乡建设厅网站越南语网站怎么做
  • React + ECharts 实践:构建可交互的数据可视化组件
  • Devconnect 活动报名中!dAI 路线图、跨链 / 预言机创新新动态!Linera 实战+Web3 安全公开课上线!
  • 华为、阿里巴巴、字节跳动 100+ Linux面试问题总结(一)
  • [OpenHarmony6.0][Docker][环境]OHOS6 编译环境构建指南
  • 空包网站建设属于哪类网站排名优化推广厦门
  • async await 的前世今生
  • 外卖项目day02
  • 多电压输出场景下ASP3605负载调整率的一致性验证
  • 使用rust复刻linux经典命令:wc(文本统计工具)
  • 网站设计公司哪里好镇江网站建设找思创网络
  • 45_FastMCP 2.x 中文文档之FastMCP集成:Azure (Entra ID) 指南
  • 【微服务中间件】RabbitMQ 全方位解析:同步异步对比、SpringAMQT基础入门、实战、交换机类型及消息处理详解
  • 单点高ROI场景医疗AI编程分析与实践
  • 使用python进行PostgreSQL 数据库连接
  • 天线类型和指标介绍