跳到主要内容Vue Router 进阶实战:导航守卫、嵌套路由与状态管理 | 极客日志JavaScriptNode.js大前端
Vue Router 进阶实战:导航守卫、嵌套路由与状态管理
Vue Router 高级用法涵盖导航守卫的权限控制逻辑、嵌套路由的结构设计、历史模式配置差异及元信息应用。对比 Vuex 与 Pinia 的状态管理方案,演示如何在 Pinia 中结合持久化插件实现数据本地存储,为构建复杂单页应用提供完整的路由与状态解决方案。
flc3 浏览 深入使用
导航守卫
在实际项目中,我们往往需要在跳转前后拦截请求,比如做权限校验。这就是导航守卫的作用。
- to 和 from 是即将进入的目标路由和当前导航正要离开的路由
- next 是一个函数,用于控制路由的跳转逻辑
- next():继续执行路由
- next(false):中断当前路由,如果浏览器的 URL 改变了,那么 URL 会回到 from 路由
- next('/path'):跳转到指定路径
- next(error):将控制权交给
router.onError()
组件内守卫
export default {
beforeRouteEnter(to, from, next) {
next();
},
beforeRouteUpdate(to, from, next) {
next();
},
beforeRouteLeave(to, from, next) {
next();
}
};
路由独享守卫
这种守卫写在路由配置里,只在进入该路由时触发。
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (isAdmin()) {
next();
} else {
next('/login');
}
}
}
];
全局后置钩子
通过 router.afterEach 设置,它不会接收 next 函数也不会改变导航本身,适合用来记录日志或统计页面访问。
router.afterEach((to, from) => {
console.log('路由已切换');
});
全局解析守卫
通过 router.beforeResolve 设置,它在所有组件内守卫和异步路由组件被解析之后调用,比前置守卫更晚触发。
router.beforeResolve((to, from, next) => {
next();
});
全局前置守卫
router.beforeEach((to, from, next) => {
if (to.path === '/protected' && !isLoggedIn()) {
next('/login');
} else {
next();
}
});
嵌套路由
嵌套路由用于构建具有层级关系的页面结构,即在一个组件内部再嵌套另一个路由视图。
需要放置一个 <router-view> 来渲染子路由组件。
<template>
<div>
<h2>用户页面</h2>
<router-view></router-view>
</div>
</template>
const routes = [
{
path: '/user',
component: User,
children: [
{
path: 'profile',
component: UserProfile
},
{
path: 'posts',
component: UserPosts
}
]
}
];
注意,以 / 开头的嵌套路径将被视为根路径。这允许你利用组件嵌套,而不必使用嵌套的 URL。
重定向与别名
- 导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上。在上面的例子中,在 /home 路由中添加 beforeEnter 守卫不会有任何效果。
- 在写 redirect 的时候,可以省略 component 配置,因为它从来没有被直接访问过,所以没有组件要渲染。唯一的例外是嵌套路由:如果一个路由记录有 children 和 redirect 属性,它也应该有 component 属性。
const routes = [
{
path: '/users/:id/posts',
redirect: to => {
return to.path.replace(/posts$/, 'profile');
}
}
];
const routes = [{ path: '/home', redirect: '/' }];
const routes = [{ path: '/home', redirect: { name: 'homepage' } }];
const routes = [
{
path: '/search/:searchText',
redirect: to => {
return {
path: '/search',
query: { q: to.params.searchText }
};
}
},
{
path: '/search'
}
];
如果路由有参数,需确保在任何绝对别名中包含它们。别名意味着 URL 保持不变,但匹配的是另一个路径。
const routes = [
{
path: '/users/:id',
component: UsersByIdLayout,
children: [
{
path: 'profile',
component: UserDetails,
alias: ['/:id', '']
}
]
}
];
通过别名,可以自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制。使别名以 / 开头,以使嵌套路径中的路径成为绝对路径。甚至可以将两者结合起来,用一个数组提供多个别名。
const routes = [
{
path: '/users',
component: UsersLayout,
children: [
{
path: '',
component: UserList,
alias: ['/people', 'list']
}
]
}
];
将 / 别名为 /home,意味着当用户访问 /home 时,URL 仍然是 /home,但会被匹配为用户正在访问 /。重定向是指当用户访问 /home 时,URL 会被 / 替换,然后匹配成 /。
const routes = [
{
path: '/',
component: Homepage,
alias: '/home'
}
];
history 配置:指定历史模式
- Hash 模式:内部传递的实际 URL 之前会加一个井号(
#),如 http://example.com/#/home。无需服务器配置,适合纯前端单页应用(SPA),但在 SEO 中确实有不好的影响。
- History 模式:url 更简洁(如
http://example.com/home),无 #。需要服务器支持(避免 404 错误,需配置回退到 index.html)。适合需要 SEO 或对 url 美观有要求的项目。
- Memory 模式:不会假定自己处于浏览器环境,因此不会与 URL 交互也不会自动触发初始导航。需要在调用
app.use(router) 之后手动 push 到初始导航。非常适合 Node 环境和 SSR。
createWebHashHistory():Hash 模式(默认模式)
import { createRouter, createWebHashHistory } from 'vue-router';
const router = createRouter({
history: createWebHashHistory(),
routes: [...]
});
createWebHistory():History 模式
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [...]
});
createMemoryHistory():Memory 模式
import { createRouter, createMemoryHistory } from 'vue-router';
const router = createRouter({
history: createMemoryHistory(),
routes: [...]
});
路由元信息(meta)
路由配置中可以添加元信息(meta),例如权限控制、页面标题等。
const routes = [
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
meta: {
requiresAuth: true,
title: '控制面板'
}
}
];
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
return { name: 'Login' };
}
});
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute();
console.log(route.meta.title);
</script>
进阶实践
状态管理(Pinia / Vuex)
对于复杂或跨页面的数据传递,推荐使用状态管理库(Pinia/Vuex)。
Pinia / Vuex 介绍
- Vuex:Vue.js 官方的状态管理库。采用了 Flux 的思想,提供了一个集中式存储,允许组件以不同的方式共享状态。核心概念包括 State(状态存储)、Getters(计算属性)、Mutations(唯一修改状态的方式)、Actions(处理异步操作)。
- Pinia:Vue.js 的官方推荐替代方案。它为 Vue 3 提供了一种更简单、更具灵活性的状态管理方式。相比 Vuex,Pinia 在设计上更现代化,支持 Composition API,使得开发者在使用时更加方便。核心概念包括 Store(状态存储基本单位)、State、Getters、Actions。
Vuex vs Pinia
- API 和易用性:Vuex 采用认证的 Flux 风格,有一定的学习曲线;Pinia 设计更为简单,结合了 Composition API,让开发者能够更直观地管理状态。
- 响应式:Vuex 响应式依赖于 Vue 的响应式系统且有多种约定;Pinia 完全基于 Vue 3 的响应式系统,状态变化后组件自动更新。
- DevTools 支持:两者都支持 Vue DevTools,Pinia 整体使用体验更友好。
- 社区支持和生态:Vuex 资料丰富;Pinia 作为新兴库,社区支持还在不断扩大。
总结:Vuex 和 Pinia 各有优缺点。如果你正在开发较大型的应用且已有 Vuex 经验,继续使用可能不错;如果想要尝试更简单、现代化的方式,Pinia 值得尝试。
Pinia 基本使用
- pinia-plugin-persistedstate 为 pinia 的数据持久化插件。
npm install pinia pinia-plugin-persistedstate
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
doubleCount(state) {
return state.count * 2;
}
},
actions: {
increment() {
this.count++;
},
async incrementAsync() {
await new Promise((resolve) => setTimeout(resolve, 1000));
this.count++;
}
},
persist: {
enabled: true,
storage: window.localStorage
}
});
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import { createPersistedState } from 'pinia-plugin-persistedstate';
const app = createApp(App);
const pinia = createPinia();
pinia.use(createPersistedState());
app.use(pinia);
app.mount('#app');
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
<button @click="incrementAsync">Increment Async</button>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useUserStore } from '@/utils/store.js';
const counterStore = useUserStore();
const count = computed(() => counterStore.count);
const doubleCount = computed(() => counterStore.doubleCount);
const increment = () => counterStore.increment();
const incrementAsync = async () => await counterStore.incrementAsync();
</script>
Pinia 使用 Cookies 存储
虽然 Cookies 主要用于存储少量数据,并且有大小限制(通常为 4KB),但在某些情况下,也可以用于简单的持久化。
使用 Cookies 进行持久化时,需要手动处理数据的读写:
export const useUserStore = defineStore('user', {
state: () => ({ name: null, age: null }),
actions: {
setName(name) {
this.name = name;
document.cookie = `name=${name}; path=/; max-age=3600`;
},
getName() {
const name = document.cookie.split('; ').find(row => row.startsWith('name='));
if (name) {
this.name = name.split('=')[1];
}
}
}
});
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online