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

前端面试专栏-主流框架:14. Vue Router与Vuex核心应用

🔥 欢迎来到前端面试通关指南专栏!从js精讲到框架到实战,渐进系统化学习,坚持解锁新技能,祝你轻松拿下心仪offer。
前端面试通关指南专栏主页
前端面试专栏规划详情在这里插入图片描述

Vue Router与Vuex核心应用深度解析:构建高效Vue应用的双引擎

在Vue.js生态体系中,Vue Router与Vuex是开发者构建大型单页应用(SPA)不可或缺的两大核心工具。Vue Router负责实现应用内的页面导航与路由控制,让用户在不同视图间无缝切换;Vuex则专注于全局状态管理,确保数据在组件间的统一与高效流转。二者相辅相成,共同为打造高性能、可维护的Vue应用奠定基础。本文将深入剖析Vue Router与Vuex的核心原理、应用场景及实践技巧。

一、Vue Router核心应用

1.1 基础路由配置

Vue Router的基础使用始于路由配置。在Vue项目中,首先需要通过npm或yarn安装vue-router

npm install vue-router@4
# 或
yarn add vue-router@4

然后在项目入口文件(如main.js)或独立路由文件中进行配置。以下是创建一个完整的浏览器路由配置示例:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';
import NotFound from '../views/NotFound.vue';const routes = [{path: '/',name: 'Home',component: Home,meta: {title: '首页',requiresAuth: false}},{path: '/about',name: 'About',component: About,meta: {title: '关于我们',requiresAuth: true}},{path: '/:pathMatch(.*)*',name: 'NotFound',component: NotFound}
];const router = createRouter({// 使用HTML5历史模式history: createWebHistory(process.env.BASE_URL),routes,// 可选:配置全局路由行为scrollBehavior(to, from, savedPosition) {if (savedPosition) {return savedPosition;} else {return { top: 0 };}}
});// 全局路由守卫示例
router.beforeEach((to, from, next) => {document.title = to.meta.title || '默认标题';next();
});export default router;

配置说明:

  1. createRouter创建路由实例,支持两种历史模式:

    • createWebHistory: HTML5模式,需要服务器配合
    • createWebHashHistory: 哈希模式,兼容性更好
  2. routes配置项详解:

    • path: 匹配的URL路径
    • name: 路由唯一标识名
    • component: 对应的视图组件
    • meta: 路由元信息,可用于权限控制等
    • 动态路径参数通过:param形式定义
    • 通配路由*用于处理404页面
  3. 高级配置:

    • scrollBehavior: 控制页面滚动行为
    • 全局路由守卫:用于权限验证、页面标题设置等

实际应用时,还需要在main.js中挂载路由:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'const app = createApp(App)
app.use(router)
app.mount('#app')

1.2 动态路由

动态路由是Vue Router的核心功能之一,它允许开发者在URL路径中定义可变参数,从而实现通过同一路由组件展示不同内容的效果。这种机制在构建需要动态展示数据的页面时尤为实用,比如用户详情页、商品详情页等场景。

路由配置

在路由配置文件中,通过在路径中使用冒号(:)前缀来定义动态参数。例如,要创建一个带用户ID参数的用户详情页路由:

const routes = [{path: '/user/:id',  // 动态参数idname: 'UserDetail', // 路由名称component: () => import('./views/UserDetail.vue'), // 路由组件props: true // 可选:将params作为props传递给组件}
];
参数获取方式

在路由组件中,获取动态参数主要有两种方式:

  1. Options API方式
    使用$route对象来访问当前路由信息,其中params属性包含了所有动态参数。
<!-- UserDetail.vue(Options API) -->
<template><div class="user-detail"><h2>用户详情</h2><p>用户ID:{{ $route.params.id }}</p><!-- 其他用户信息展示 --></div>
</template><script>
export default {name: 'UserDetail',created() {console.log('当前用户ID:', this.$route.params.id);// 可以在此处根据ID获取用户详情数据}
};
</script>
  1. Composition API方式
    使用useRoute组合式函数来获取路由信息,这种方式更适合setup语法。
<!-- UserDetail.vue(Composition API) -->
<template><div class="user-detail"><h2>用户详情</h2><p>用户ID:{{ userId }}</p><!-- 其他用户信息展示 --></div>
</template><script setup>
import { useRoute } from 'vue-router';
import { ref, onMounted } from 'vue';const route = useRoute();
const userId = ref(route.params.id);onMounted(() => {console.log('当前用户ID:', userId.value);// 可以在此处根据ID获取用户详情数据
});
</script>
高级用法
  1. 多参数路由
{path: '/product/:category/:id',component: ProductDetail
}
// 访问/product/electronics/123时
// params = { category: 'electronics', id: '123' }
  1. 可选参数
{path: '/user/:id?', // 问号表示可选component: UserProfile
}
// 既可匹配/user/123,也可匹配/user
  1. 正则约束
{path: '/user/:id(\\d+)', // 仅匹配数字IDcomponent: UserDetail
}
  1. 参数作为props
{path: '/user/:id',props: true, // 将params作为props传递component: UserDetail
}
// 组件中可直接通过props接收

动态路由为构建灵活的前端应用提供了强大支持,开发者可以根据实际需求选择最适合的参数获取方式。在使用时要注意参数的类型验证和错误处理,特别是在通过参数发起异步请求时,应做好加载状态和错误状态的UI处理。

1.3 嵌套路由

嵌套路由是构建多层级页面结构的有效方式,特别适用于具有父子关系的视图场景。比如在以下典型应用中:

  1. 博客系统:

    • 父路由:/blog 显示文章列表
    • 子路由:/blog/:articleId 显示具体文章内容
  2. 电商平台:

    • 父路由:/products 展示商品分类
    • 子路由:/products/:categoryId 展示具体分类商品

配置示例:

const routes = [{path: '/blog',component: BlogList,// 定义子路由children: [{path: ':articleId',  // 动态路径参数component: ArticleDetail,props: true  // 将路由参数作为props传递},{path: 'new',  // 可添加多个子路由component: NewArticle}]}
];

使用要点:

  1. 父组件中必须包含<router-view>作为子组件的渲染出口:
<template><div class="blog-container"><h1>博客列表</h1><div class="sidebar"><!-- 文章列表导航 --></div><div class="content"><!-- 子路由内容将在此渲染 --><router-view :key="$route.fullPath"></router-view></div></div>
</template>
  1. 路由匹配规则:

    • 完整路径 = 父路径 + 子路径
    • 如:/blog/123 将匹配文章ID为123的详情页
    • 可通过$route.params获取动态参数
  2. 实际开发技巧:

    • 使用命名视图可支持更复杂的布局
    • 添加redirect属性可设置默认子路由
    • 嵌套路由也支持路由守卫和懒加载

1.4 路由守卫

路由守卫是Vue Router提供的重要功能,它允许开发者在路由导航的不同阶段进行拦截和逻辑控制,从而实现诸如权限验证、数据预加载、页面访问限制等常见需求。根据作用范围和时机的不同,路由守卫主要分为以下三种类型:

1. 全局守卫

全局守卫对整个应用的所有路由跳转都有效,通过Router实例的方法进行注册。最常用的三种全局守卫包括:

  • beforeEach:在路由跳转前触发,常用于权限验证
  • beforeResolve:在所有组件守卫和异步路由组件解析后触发
  • afterEach:在路由跳转完成后触发,常用于页面追踪

典型应用场景:用户登录验证

router.beforeEach((to, from, next) => {// 获取本地存储的tokenconst isLoggedIn = localStorage.getItem('token');// 检查目标路由是否需要认证if (to.meta.requiresAuth && !isLoggedIn) {// 未登录则跳转到登录页next('/login');} else if (to.path === '/login' && isLoggedIn) {// 已登录却访问登录页则跳转到首页next('/');} else {// 正常放行next();}
});
2. 路由独享守卫

路由独享守卫仅在路由配置对象中通过beforeEnter定义,仅对当前配置的路由生效。适用于特定路由的特殊处理。

使用示例:

const routes = [{path: '/dashboard',component: Dashboard,beforeEnter: (to, from, next) => {// 检查用户权限if (!checkUserPermission()) {next('/unauthorized');} else {next();}}}
]
3. 组件内守卫

组件内守卫直接在Vue组件中定义,只影响该组件相关的路由行为,主要包括:

  • beforeRouteEnter:进入组件前触发,此时组件实例还未创建
  • beforeRouteUpdate:当前路由改变但组件被复用时触发
  • beforeRouteLeave:离开当前路由前触发,常用于表单未保存提示

组件内守卫示例:

export default {beforeRouteLeave(to, from, next) {if (this.formChanged) {const answer = confirm('您有未保存的更改,确定要离开吗?');answer ? next() : next(false);} else {next();}}
}

在实际开发中,这三种守卫类型可以组合使用,通过不同的钩子函数实现精细化的路由控制逻辑。

二、Vuex核心应用

2.1 核心概念与基础使用

Vuex作为Vue.js的官方状态管理库,采用集中式存储管理应用的所有组件的状态,其核心概念包括以下几种基本元素:

State(状态)

State是Vuex中存储应用状态数据的对象,是整个应用的唯一数据源。它类似于组件的data选项,但它是全局共享的。每个组件都可以直接或间接地访问这些状态。

示例:定义一个包含计数器状态的store

// store.js
import { createStore } from 'vuex';const store = createStore({state: {count: 0,               // 计数器状态todos: [                // 待办事项列表{ id: 1, text: '学习Vuex', done: false },{ id: 2, text: '编写文档', done: true }]}
});export default store;
Getter(获取器)

Getter相当于Vuex中的计算属性,用于从state中派生出一些新状态。Getter的结果会被缓存,只有当它的依赖值发生变化时才会重新计算。

计算属性示例:

const store = createStore({state: {count: 0},getters: {doubleCount: state => state.count * 2,  // 计算翻倍后的计数doneTodos: state => state.todos.filter(todo => todo.done),  // 筛选已完成待办项getTodoById: state => id => state.todos.find(todo => todo.id === id)  // 方法式访问}
});
Mutation(变更)

Mutation是唯一可以修改state的方式,每个mutation都有一个字符串类型的事件类型(type)和一个回调函数(handler)。Mutation必须是同步函数,这样devtools才能准确追踪状态变化。

示例:

const store = createStore({state: {count: 0},mutations: {increment(state) {              // 无载荷的mutationstate.count++;},incrementBy(state, payload) {   // 带载荷的mutationstate.count += payload.amount;}}
});
Action(动作)

Action用于处理异步操作,通过提交mutation来改变state。Action可以包含任意异步操作,如API调用、定时器等。

完整示例:

const store = createStore({state: {count: 0},mutations: {increment(state) {state.count++;}},actions: {incrementAsync({ commit }) {   // 异步增加setTimeout(() => {commit('increment');}, 1000);},async fetchUserInfo({ commit }, userId) {  // API调用示例try {const response = await api.fetchUser(userId);commit('SET_USER_INFO', response.data);} catch (error) {commit('SET_ERROR', error);}}}
});
在组件中使用Vuex

在Options API中,可以使用mapState、mapGetters、mapMutations和mapActions辅助函数来简化代码:

<template><div><p>计数:{{ count }}</p><p>翻倍计数:{{ doubleCount }}</p><button @click="increment">增加</button><button @click="incrementBy({amount: 5})">增加5</button><button @click="incrementAsync">异步增加</button><ul><li v-for="todo in doneTodos" :key="todo.id">{{ todo.text }}</li></ul></div>
</template><script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';export default {computed: {...mapState(['count']),...mapGetters(['doubleCount', 'doneTodos'])},methods: {...mapMutations(['increment', 'incrementBy']),...mapActions(['incrementAsync'])}
};
</script>

在Composition API中,可以使用useStore hook:

<template><div><p>计数:{{ count }}</p><p>翻倍计数:{{ doubleCount }}</p><button @click="increment">增加</button><button @click="incrementAsync">异步增加</button></div>
</template><script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';const store = useStore();// 访问state
const count = computed(() => store.state.count);// 访问getters
const doubleCount = computed(() => store.getters.doubleCount);// 提交mutation
const increment = () => store.commit('increment');// 分发action
const incrementAsync = () => store.dispatch('incrementAsync');
</script>
模块化组织

当应用规模扩大时,可以将store分割成模块:

const moduleA = {state: () => ({ ... }),mutations: { ... },actions: { ... },getters: { ... }
}const store = createStore({modules: {a: moduleA}
})

在组件中访问模块状态:

computed: {...mapState({aCount: state => state.a.count})
}

2.2 模块化管理

随着前端应用复杂度提升,单一Store文件会导致代码臃肿、维护困难。将Vuex Store拆分为功能独立的模块是推荐的最佳实践,每个模块管理特定业务领域的状态数据。

模块化优势
  1. 代码组织结构清晰:按业务功能划分模块,如用户、商品、订单等
  2. 避免命名冲突:各模块有独立命名空间
  3. 便于团队协作:不同开发者可以并行开发不同模块
  4. 按需加载:支持动态注册模块
基础实现示例
// store.js
import { createStore } from 'vuex';
// 导入子模块
import user from './modules/user';
import product from './modules/product';
import cart from './modules/cart';const store = createStore({// 注册模块modules: {user,      // 用户相关状态product,   // 商品相关状态 cart       // 购物车状态},// 可配置全局状态和操作state: {appVersion: '1.0.0'}
});export default store;
模块文件结构示例

建议按以下结构组织模块文件:

src/store/index.js       # 主入口文件modules/user.js      # 用户模块product.js   # 商品模块cart.js      # 购物车模块
完整模块示例
// modules/user.js
const user = {// 开启命名空间namespaced: true,state: () => ({name: '',age: 0,token: null}),getters: {// 带命名空间的getterfullName: state => `${state.name}${state.age}岁)`,isAuthenticated: state => !!state.token},mutations: {setName(state, payload) {state.name = payload.name;},setToken(state, token) {state.token = token;}},actions: {// 异步登录操作async login({ commit }, credentials) {try {const response = await api.login(credentials);commit('setToken', response.data.token);return response;} catch (error) {console.error('登录失败:', error);throw error;}},// 模拟异步获取用户数据asyncFetchUser({ commit }) {return new Promise(resolve => {setTimeout(() => {commit('setName', { name: '张三' });resolve();}, 1000);});}}
};export default user;
组件中使用方式

在组件中访问模块状态和方法:

// 访问状态
computed: {userName() {return this.$store.state.user.name;},userAge() {return this.$store.state.user.age;}
}// 调用action
methods: {login() {this.$store.dispatch('user/login', {username: 'test',password: '123456'});}
}// 使用map辅助函数
import { mapState, mapActions } from 'vuex';export default {computed: {...mapState('user', ['name', 'age'])},methods: {...mapActions('user', ['login'])}
}
注意事项
  1. 建议为每个模块设置namespaced: true以避免命名冲突
  2. 模块可以嵌套,形成层级结构
  3. 动态模块可以使用store.registerModule()注册
  4. 模块间通信可通过rootState或rootGetters实现

2.3 插件扩展机制

Vuex 提供了强大的插件系统,允许开发者通过插件来扩展 Vuex 的核心功能。插件通常用于实现以下几种常见需求:

  • 数据持久化
  • 状态变更日志记录
  • 错误捕获
  • 性能监控
  • 表单处理等
插件工作原理

Vuex 插件是一个函数,它接收 store 作为唯一参数:

const myPlugin = (store) => {// 插件初始化逻辑store.subscribe((mutation, state) => {// 每次 mutation 后调用console.log(mutation.type, mutation.payload)})
}
数据持久化示例:vuex-persist

一个典型的应用场景是使用 vuex-persist 插件来实现状态数据的本地存储:

  1. 安装插件:
npm install vuex-persist
# 或者
yarn add vuex-persist
  1. 基本使用方式:
import { createStore } from 'vuex'
import createPersistedState from 'vuex-persist'// 创建插件实例
const vuexPersist = new createPersistedState({key: 'my-vuex-storage', // 本地存储的 keystorage: window.localStorage, // 也可以使用 sessionStoragereducer: (state) => ({ count: state.count }), // 可选:指定要持久化的状态filter: (mutation) => mutation.type === 'increment' // 可选:过滤需要触发存储的 mutation
})const store = createStore({state() {return {count: 0,temporaryData: null // 不会被持久化的数据}},mutations: {increment(state) {state.count++},reset(state) {state.count = 0}},plugins: [vuexPersist]
})
  1. 插件功能说明:
  • 自动将指定状态保存到 localStorage
  • 页面刷新或重新打开时会自动恢复状态
  • 支持定制存储策略(如只保存部分状态)
  • 支持多种存储方式(localStorage/sessionStorage/cookie等)
其他常用插件
  1. 日志插件
const logger = store => {store.subscribe((mutation, state) => {console.log('Mutation:', mutation)console.log('New state:', state)})
}
  1. 多 Tab 同步
// 当其他 tab 修改状态时同步更新当前 tab
window.addEventListener('storage', (event) => {if (event.key === 'my-vuex-storage') {store.replaceState(JSON.parse(event.newValue))}
})

在实际项目中,可以根据需求组合使用多个插件,比如同时使用数据持久化和日志插件:

plugins: [createPersistedState(),logger
]

注意事项:

  • 插件会按照数组顺序执行
  • 不要在插件中直接修改 state,应该通过提交 mutation
  • 生产环境可能需要移除日志插件等调试工具

三、Vue Router与Vuex的协同应用

在实际项目中,Vue Router与Vuex的紧密配合能够构建出更加完善的SPA应用。以下是几个典型应用场景:

1. 用户认证流程管理

  • 登录场景:用户提交登录表单后,服务端验证成功返回token。此时:

    1. 将token存储到Vuex的state中
    2. 更新Vuex中的isAuthenticated状态为true
    3. 通过router.push('/dashboard')跳转到用户主页
    4. 用户信息存储在Vuex的user模块中
  • 权限控制:在路由配置中使用全局前置守卫:

router.beforeEach((to, from, next) => {if (to.meta.requiresAuth && !store.state.auth.isAuthenticated) {next('/login')} else {next()}
})

2. 页面状态管理

  • 加载状态控制

    1. 路由切换时,在Vuex中设置isLoading: true
    2. 页面完全加载后,设置为false
    3. 在App组件中监听该状态,控制全局加载动画
    <template><div id="app"><loading-spinner v-if="$store.state.isLoading"/><router-view/></div>
    </template>
    
  • 路由参数同步
    将重要路由参数同步到Vuex中,方便组件访问:

    watch: {'$route.params.id'(newVal) {this.$store.commit('SET_CURRENT_ID', newVal)}
    }
    

3. 数据预加载

在导航守卫中触发Vuex action预加载数据:

router.beforeResolve((to, from, next) => {if (to.matched.some(record => record.meta.requiresData)) {store.dispatch('fetchRequiredData').then(next)} else {next()}
})

4. 状态持久化

结合Vuex的插件系统,实现:

  1. 路由变化时自动保存特定状态到localStorage
  2. 应用初始化时从本地存储恢复状态
  3. 同步Vuex状态与路由参数

这种协同模式使得应用的数据流与页面导航形成闭环,既保证了状态的可控性,又提供了流畅的用户体验。通过合理设计,可以避免组件间的直接通信,让数据流向更加清晰可维护。

Vue Router与Vuex作为Vue生态的核心利器,在构建现代前端应用中发挥着关键作用。熟练掌握它们的核心原理与应用技巧,能够帮助开发者打造出功能强大、体验出色的Vue应用。在实际开发过程中,开发者应根据项目需求灵活运用二者的特性,不断优化应用架构,提升开发效率与应用质量。

📌 下期预告:Vue 模板编译与渲染流程
❤️❤️❤️:如果你觉得这篇文章对你有帮助,欢迎点赞、关注本专栏!后续解锁更多功能,敬请期待!👍🏻 👍🏻 👍🏻
更多专栏汇总:
前端面试专栏
Node.js 实训专栏

http://www.lqws.cn/news/535807.html

相关文章:

  • Spring Boot使用Redis常用场景
  • Python爬虫多线程并发时的503错误处理最佳实践
  • HTTP-Cookie和Session
  • 算法第48天|单调栈:42. 接雨水、84.柱状图中最大的矩形
  • 鸿蒙边缘智能计算架构实战:从环境部署到分布式推理全流程
  • window显示驱动开发—DirectX 图形内核子系统(一)
  • 树莓派超全系列教程文档--(67)rpicam-apps可用选项介绍之检测选项
  • 算法-最大子数组
  • 【Python】For
  • Agentic AI爆发前夜,合作伙伴如何把握时代机遇?
  • 2D写实交互数字人如何重塑服务体验?
  • MP1652GTF-Z:MPS高效3A降压转换器 工业5G通信专用
  • windows内核句柄判断有效
  • LeetCode刷题-top100(和为 K 的子数组)
  • ISP Pipeline(4): Anti Aliasing Noise Filter 抗锯齿与降噪滤波器
  • 【thinkphp5】Session和Cache记录微信accesstoken
  • QT实现一个三轴位移台的控制界面
  • QT Creator构建失败:-1: error: Unknown module(s) in QT: serialport
  • 【CMake基础入门教程】第七课:查找并使用第三方库(以 find_package() 为核心)
  • 【缓存技术】深入分析如果使用好缓存及注意事项
  • Flux.create
  • Linux 内核 TCP 的核心引擎:tcp_input.c 与 tcp_output.c 的协同之道
  • ubuntu安装docker遇到权限问题
  • TCP 重传机制详解:原理、变体与故障排查应用
  • 利用python和libredwg库解析dwg格式文件输出GeoJSON
  • Mac电脑如何搭建基于java后端的开发的各种工具服务
  • 自动获取文件的内存大小怎么设置?批量获取文件名和内存大小到Excel中的方法
  • IDEA下载不了插件了怎么办?从本地导入插件详细教程!
  • ubuntu 远程桌面 xrdp + frp
  • 【工具推荐】WaybackLister——发现潜在目录列表