前端面试专栏-主流框架: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;
配置说明:
-
createRouter
创建路由实例,支持两种历史模式:createWebHistory
: HTML5模式,需要服务器配合createWebHashHistory
: 哈希模式,兼容性更好
-
routes
配置项详解:path
: 匹配的URL路径name
: 路由唯一标识名component
: 对应的视图组件meta
: 路由元信息,可用于权限控制等- 动态路径参数通过
:param
形式定义 - 通配路由
*
用于处理404页面
-
高级配置:
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传递给组件}
];
参数获取方式
在路由组件中,获取动态参数主要有两种方式:
- 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>
- 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>
高级用法
- 多参数路由:
{path: '/product/:category/:id',component: ProductDetail
}
// 访问/product/electronics/123时
// params = { category: 'electronics', id: '123' }
- 可选参数:
{path: '/user/:id?', // 问号表示可选component: UserProfile
}
// 既可匹配/user/123,也可匹配/user
- 正则约束:
{path: '/user/:id(\\d+)', // 仅匹配数字IDcomponent: UserDetail
}
- 参数作为props:
{path: '/user/:id',props: true, // 将params作为props传递component: UserDetail
}
// 组件中可直接通过props接收
动态路由为构建灵活的前端应用提供了强大支持,开发者可以根据实际需求选择最适合的参数获取方式。在使用时要注意参数的类型验证和错误处理,特别是在通过参数发起异步请求时,应做好加载状态和错误状态的UI处理。
1.3 嵌套路由
嵌套路由是构建多层级页面结构的有效方式,特别适用于具有父子关系的视图场景。比如在以下典型应用中:
-
博客系统:
- 父路由:/blog 显示文章列表
- 子路由:/blog/:articleId 显示具体文章内容
-
电商平台:
- 父路由:/products 展示商品分类
- 子路由:/products/:categoryId 展示具体分类商品
配置示例:
const routes = [{path: '/blog',component: BlogList,// 定义子路由children: [{path: ':articleId', // 动态路径参数component: ArticleDetail,props: true // 将路由参数作为props传递},{path: 'new', // 可添加多个子路由component: NewArticle}]}
];
使用要点:
- 父组件中必须包含
<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>
-
路由匹配规则:
- 完整路径 = 父路径 + 子路径
- 如:/blog/123 将匹配文章ID为123的详情页
- 可通过$route.params获取动态参数
-
实际开发技巧:
- 使用命名视图可支持更复杂的布局
- 添加
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拆分为功能独立的模块是推荐的最佳实践,每个模块管理特定业务领域的状态数据。
模块化优势
- 代码组织结构清晰:按业务功能划分模块,如用户、商品、订单等
- 避免命名冲突:各模块有独立命名空间
- 便于团队协作:不同开发者可以并行开发不同模块
- 按需加载:支持动态注册模块
基础实现示例
// 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'])}
}
注意事项
- 建议为每个模块设置
namespaced: true
以避免命名冲突 - 模块可以嵌套,形成层级结构
- 动态模块可以使用
store.registerModule()
注册 - 模块间通信可通过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
插件来实现状态数据的本地存储:
- 安装插件:
npm install vuex-persist
# 或者
yarn add vuex-persist
- 基本使用方式:
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]
})
- 插件功能说明:
- 自动将指定状态保存到 localStorage
- 页面刷新或重新打开时会自动恢复状态
- 支持定制存储策略(如只保存部分状态)
- 支持多种存储方式(localStorage/sessionStorage/cookie等)
其他常用插件
- 日志插件:
const logger = store => {store.subscribe((mutation, state) => {console.log('Mutation:', mutation)console.log('New state:', state)})
}
- 多 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。此时:
- 将token存储到Vuex的state中
- 更新Vuex中的
isAuthenticated
状态为true - 通过
router.push('/dashboard')
跳转到用户主页 - 用户信息存储在Vuex的
user
模块中
-
权限控制:在路由配置中使用全局前置守卫:
router.beforeEach((to, from, next) => {if (to.meta.requiresAuth && !store.state.auth.isAuthenticated) {next('/login')} else {next()}
})
2. 页面状态管理
-
加载状态控制:
- 路由切换时,在Vuex中设置
isLoading: true
- 页面完全加载后,设置为
false
- 在App组件中监听该状态,控制全局加载动画
<template><div id="app"><loading-spinner v-if="$store.state.isLoading"/><router-view/></div> </template>
- 路由切换时,在Vuex中设置
-
路由参数同步:
将重要路由参数同步到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的插件系统,实现:
- 路由变化时自动保存特定状态到localStorage
- 应用初始化时从本地存储恢复状态
- 同步Vuex状态与路由参数
这种协同模式使得应用的数据流与页面导航形成闭环,既保证了状态的可控性,又提供了流畅的用户体验。通过合理设计,可以避免组件间的直接通信,让数据流向更加清晰可维护。
Vue Router与Vuex作为Vue生态的核心利器,在构建现代前端应用中发挥着关键作用。熟练掌握它们的核心原理与应用技巧,能够帮助开发者打造出功能强大、体验出色的Vue应用。在实际开发过程中,开发者应根据项目需求灵活运用二者的特性,不断优化应用架构,提升开发效率与应用质量。
📌 下期预告:Vue 模板编译与渲染流程
❤️❤️❤️:如果你觉得这篇文章对你有帮助,欢迎点赞、关注本专栏!后续解锁更多功能,敬请期待!👍🏻 👍🏻 👍🏻
更多专栏汇总:
前端面试专栏
Node.js 实训专栏