共计 10170 个字符,预计需要花费 26 分钟才能阅读完成。
目录
路由的基本概念
1. 路由
2. 单页应用 SPA
3. 前端路由的实现方式
3.1Hash 模式
3.2History 模式
Vue router 4
1. 概述
2. 安装使用
3. 基础用法
3.1 路由匹配规则声明
3.2 动态路由匹配
3.3 路由命名
3.4 路由重定向
3.5 路由嵌套
3.6 命名视图
3.6 声明式导航 & 编程式导航
3.7 历史记录模式
4. 进阶用法
4.1 导航守卫
4.11 全局前置守卫
4.12 全局后置钩子
4.13 路由独享的守卫
4.14 组件内的守卫
4.2 路由元信息
4.3RouterView 插槽
路由的基本概念
1. 路由
在前端开发中,路由(Routing)是一个核心概念,尤其是在单页应用(SPA)中。它描述的是 URL 与 UI 之间的映射关系,即当用户访问不同的 URL 时,前端应用会相应地展示不同的界面或组件,而 无需重新从服务器加载整个页面。
2. 单页应用 SPA
SPA 指的是一个 web 网站只加载单个 HTML 页面,所有组件的展示与切换都在唯一的一个页面内完成。它 通过路由切换界面(或组件),以及网络请求数据,实现页面的局部刷新或整体更新,而无需重新加载整个页面。
得益于 无需全部重加载 ,SPA 具有很多优点,尤其是 页面切换快,减轻服务器压力。
3. 前端路由的实现方式
3.1Hash 模式
利用 URL 的 hash 值(即 #后面的部分)来模拟一个完整的 URL,以便在前端进行路由的匹配和页面的渲染。
①用户点击路由链接,导致 hash 值变化
②hash 值变化,触发 hashchange 事件(而不会重新加载页面)
③路由监听 hashchange 事件的触发,根据 hash 值的变化来更新页面的内容
简单实现:
①HTML 结构
②JS 逻辑
window.onload = function () {
// 初始化页面
updateView()
// 监听 hashchange 事件
window.addEventListener('hashchange', function () {updateView()
})
function updateView() {// 根据 URL 进行界面或组件的切换操作,如此处的更新 的渲染内容
const hash = window.location.hash
let content = ''
switch (hash) {
case '#/home':
content = '首页
'
break
case '#/about':
content = '关于
'
break
case '#/contact':
content = '联系我们
'
break
default:
content = '404 Not Found
'
}
document.getElementById('app').innerHTML = content
}
}
Hash 模式,浏览器兼容性强,简单易用不需要服务器端的支持。但 URL 中包含了 #号可能不太美观
3.2History 模式
在 History 模式下,前端路由的实现方式与 Hash 模式 类似,但URL 的变化和监听机制不同。
①利用 HTML5 History API(如 history.pushState 和 history.replaceState 方法)来实现 URL 的跳转,而不是直接设置 window.location.hash。
②监听 popstate 事件,根据 URL 更新页面内容
由于 History 模式的实现相对复杂,并且需要服务器端的支持,因此在实际项目中,通常会使用 Vue Router、React Router 等前端路由库来简化开发。这些库已经封装了 URL 的修改、监听和页面内容更新的逻辑,并提供了丰富的路由配置选项。
Vue router 4
1. 概述
Vue Router 是 Vue.js 的官方 路由解决方案。它与 Vue.js 核心深度集成,让用 Vue 构建单页应用变得轻而易举。通过它,你可以轻松地管理应用中的页面跳转、路由守卫(如登录守卫),懒加载等功能。
(注意:以下介绍以 Vue Router 4 为基础,相比于 Vue Router 3 引入了一些新的特性和改进)
官方文档:Vue Router | Vue.js 的官方路由
2. 安装使用
以下操作环境基础为 vue 3 Composition API 和 Vue Router 4。其他版本大同小异,可自行查阅相关文档
2.1 安装 vue router
npm install vue-router@4
#
pnpm add vue-router@4
2.2 封装路由模块
在 src 下创建 router 目录,在 router 目录中创建 index.js 文件:
import {createMemoryHistory, createRouter} from "vue-router";
// 引入组件
import About from "@/view/About.vue";
import Home from "@/view//Home.vue";
// 配置路由映射规则
const routes = [{ path: "/", component: Home},
{path: "/about", component: About},
];
// 路由实例通过 createRouter 创建
const router = createRouter({history: createMemoryHistory(),
routes,
});
export default router; // 导出,用于 main.js 注册
①history 栈在浏览器中是用于存储用户会话历史(即 URL 地址)的。后面再详细介绍
②routes 数组,声明路由的匹配规则,是组件和 URL 的映射关系。
2.3 在 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");
全局注册该插件后,它会:
①全局注册 RouterView 和 RouterLink 组件
②添加全局 $router 和 $route 属性
③启用 useRouter()和 useRoute()组合式函数
④触发路由器解析初始路由
2.4 在组件中使用路由 App.vue 中
首页
关于我们
1)路由实例和路由信息对象:
①Vue Router 路由实例,它是一个全局的路由对象,包含了路由的跳转方法、钩子函数等。你可以通过它来编程式地导航到不同的 URL,或者监听路由导航事件。
②路由信息对象,包含了当前激活的路由的信息。这个对象是不可变的,每次路由切换时,Vue Router 都会更新这个对象,以反映当前路由的状态。
2)$router 和 $route 属性:
$router 是路由实例,$route 是路由信息对象,它们在 template 模板中被暴露
3) useRouter() 和 useRoute() 函数:
在 script 标签中,可以通过 useRouter() 和 useRoute() 函数来访问路由器实例和当前路由。
4)RouterView 和 RouterLink 组件:
RouterView,可将其视为一个占位符,Vue Router 会将其替换为当前路由对应的组件。
RouterLink,用于创建导航链接的组件。它默认渲染为一个 标签,但其行为会根据当前的路由来自动地切换活动的 class(当链接是激活状态时,它会自动添加一个
.router-link-active
的 class),并且当用户点击链接时,它不会执行浏览器的默认跳转,而是会使用 Vue Router 来更改 URL 并渲染对应的组件。
3. 基础用法
3.1 路由匹配规则声明
通过 routes 数组声明路由的匹配规则。path:要匹配的路由地址,component:要展示的组件
import Home from "@/view//Home.vue"
const routes = [{ path: "/home", component: Home},
// 或用动态导入代替静态导入(推荐){path: "/home", component: () => import("@/view/Home.vue") }
];
3.2 动态路由匹配
我们可以在路径中使用一个 路径参数 来将多个 URL 映射到同一个路由:
const routes = [
// 动态字段以冒号开始
{path: '/users/:username', component: User},
]
像 /users/johnny 和 /users/jack 这样的 URL 都会映射到同一个路由,即渲染同一个 User 组件。
但从 /users/johnny 导航到 /users/jac 时,组件不会经历销毁重建(复用,高效),这意味着组件的生命周期钩子不会被调用 。但 路由信息对象 的route.params.username 属性 会响应 URL 的变化,故可以监听该属性来实现某些需要。
3.3 路由命名
创建一个路由时,可选择给路由一个 唯一的 name,然后可使用 name 代替 path 来传递
routes: [{ path: '/user/:userId',name: 'user',component: User}
]
// 使用命名路由进行导航
router.push({name: 'user', params: { userId: 123}})
// 在模板中使用命名路由生成链接
用户
3.4 路由重定向
当用户访问某个路由地址时,可通过 redirect 使用户重定向访问到另一个地址。常用于防止当路由地址为 /
时什么也不显示,让网页默认显示首页
// 当用户访问 / 时 会通过 redirect 属性重定向到 /home:const routes = [{ path: '/', redirect: '/home'},// 重定向的目标也可以是一个命名的路由:{path: '/', redirect: { name: 'homepage'},
{path: "/home", component: Home,name:'homepage'},
]
3.5 路由嵌套
通过路由实现组件的嵌套展示,叫做嵌套路由。
比如,下面的 About 组件中还有 TAB1 和 TAB2 两个需要路由控制的组件
在 About 组件中声明路由链接和出口
//About.vue 组件
TAB1
TAB2
在路由封装的模块中使用 chiildren 属性声明子路由规则
const routes = [
{
path: "/about",
component: About,
children: [
{// 当 /about/tab1 匹配成功 TAB1 将被渲染到 About 的 内部
path: "tab1",
component: TAB1,
},
{ // .........
path: "tab2",
component: TAB2,
}]}]
注意:子路由的 path
不需要加 /
3.6 命名视图
有时候需要同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。它可以让我们拥有多个
....
// 定义命名视图
routes: [{
path: '/',
components: {
header: Header,
default: Home,
sidebar: Sidebar
},},
{
path:'/user'
component:User
}],
....
如果 router-view
没有设置名字,那么默认为 default
。如上面例子中:当路由地址是 / 时,会同级渲染展示 Header,Home,Sidebar 三个组件。当路由地址是 /user 时,就只会渲染 User 一个组件。
3.6 声明式导航 & 编程式导航
声明式导航:点击链接跳转路由的方式。如,普通网页中点击 链接;vue 项目中点击
编程式导航:调用 API 跳转路由的方式。如,普通网页中调用 location.href 跳转到新页面;vue 项目中借助 router 的实例方法,通过编写代码来实现跳转新页面。
获取 router 实例的方式(先作基础了解):
①template 模板中直接使用 $router 访问路由实例
②在 script 标签中使用 useRouter() 函数来访问路由器实例
// 组合式,setup 语法糖
导航方式:
①导航到不同的位置:路由实例(router) 的 push 方法,router.push(’地址‘)。这个方法会向 history 栈 添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL。
声明式导航 | 编程式导航 |
|
router.push(...) |
当你点击
时,内部会调用这个方法,所以点击
相当于调用 router.push(...)
② 替换当前位置:路由实例(router) 的 replace 方法,router.replace(’地址‘)。它的类似于 push
,唯一不同的是, 它在导航时不会向 history 添加新记录,正如它的名字 — 取代当前的条目
声明式导航 | 编程式导航 |
|
router.replace(...) |
③横跨历史:该方法采用一个整数作为参数,表示在历史堆栈中前进或后退多少步
// 向前移动一条记录,与 router.forward() 相同
router.go(1)
// 返回一条记录,与 router.back() 相同
router.go(-1)
// 前进 3 条记录
router.go(3)
// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)
3.7 历史记录模式
在前端路由中,history 栈在浏览器中是用于存储用户会话历史(即 URL 地址)的。在创建路由器实例时,history
配置允许我们在不同的历史模式中进行选择。
hash 模式(不利于 SEO)
import {createRouter, createWebHashHistory} from 'vue-router'
const router = createRouter({history: createWebHashHistory(),
routes: [],})
它在内部传递的实际 URL 之前使用了一个哈希字符(#
), 如:http://localhost:5173/#/homehttp://localhost:5173/#/home。由于这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理。不过,它在 SEO 中有不好的影响。
Memory 模式(无历史记录)
import {createRouter, createMemoryHistory} from 'vue-router'
const router = createRouter({history: createMemoryHistory(),
routes: [],})
Memory 模式不会假定自己处于浏览器环境,因此不会与 URL 交互 也不会自动触发初始导航 。这使得它非常适合 Node 环境和 SSR。但请注意 它不会有历史记录 ,这意味着你无法 后退 或前进。
HTML5 模式(推荐使用)
import {createRouter, createWebHistory} from 'vue-router'
const router = createRouter({history: createWebHistory(),
routes: [],})
优点:URL 美观,如 https://example.com/user/id
,利于 SEO。具有历史记录
4. 进阶用法
4.1 导航守卫
导航守卫类似于组件的生命周期钩子函数,会在导航跳转前,后等的特定阶段执行。
4.11 全局前置守卫
每次发生路由的 导航跳转 时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。它常用于为某些路由界面设置访问权限。
// 创建路由
const router = createRouter({...})
router.beforeEach((to, from, next) => {
//...
return 参数
})
通过 router.beforeEach
注册一个全局前置守卫。回调函数的参数:
- to 将要访问的目标路由信息对象
- from 将要离开的路由信息对象
- next(可选) 是一个函数,调用 next()表示放行,允许这次路由导航
return 可以返回的值如下:
- false:取消当前的导航。
- 路由地址:中断当前的导航,并重定向到该地址,如同调用
router.push()
,且可以传入诸如replace: true
或name: 'home'
之类的选项。
通过上面的参数我们可以为路由设置访问权限,如:设置登录访问权限
①return
router.beforeEach(async (to, from) => {
if (
// 检查用户是否已登录,且❗️ 避免无限重定向
!isAuthenticated && to.name !== 'Login'
) {
// 将用户重定向到登录页面
return {name: 'Login'}
}})
②next
router.beforeEach((to, from, next) => {
// 未登录,if (to.name !== 'Login' && !isAuthenticated) {next({ name: 'Login'}) // 重定向到登录页面
//next(false) // 或直接拒绝本次跳转
}else next() // 已登录,放行本次导航})
③ 如果多个路由地址需要设置访问权限
router.beforeEach((to, from, next) => {
// 要进行导航守卫的路径值
const pathArr = ["/home", "/home/users", "/home/rights"];
if (pathArr.indexOf(to.path) !== -1) {if (!isAuthenticated) next("/login");
else next();} else next();});
4.12 全局后置钩子
全局后置钩子和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身,但它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。
如下:点击某路由链接时,改变 document.title
...
{
path: 'navigation-guards',
component: Navigation,
meta: {isAuth: true, title: '导航守卫'},
},
...
// 全局后置守卫
router.afterEach((to, from) => {document.title = to.meta.title ||''})
4.13 路由独享的守卫
直接在路由配置上定义 beforeEnter
守卫,与全局的守卫没什么区别,只是作用的范围不同
const routes = [{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},},]
你也可以将一个函数数组传递给 beforeEnter
,这在为不同的路由重用守卫时很有用:
function fn1(to) {...}
function fn2(to) {...}
const routes = [
{ path: '/users/:id',
component: UserDetails,
beforeEnter: [fn1, fn2],
},
{ path: '/about',
component: UserDetails,
beforeEnter: [fn2],
},
]
注意:
①
beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。②当配合嵌套路由使用时,父路由和子路由都可以使用
beforeEnter
。如果放在父级路由上,路由在具有相同父级的子路由之间移动时,它不会被触发。例如:const routes = [ { path: '/user', beforeEnter() {}, children: [{ path: 'list', component: UserList}, {path: 'details', component: UserDetails}, ], }, ]
③独享路由守卫是没有后置路由守卫的
4.14 组件内的守卫
// 组合式 setup 语法糖
- beforeRouteEnter:在渲染该组件的对应路由被验证前调用。注意:不能获取组件实例 `this`!因为当守卫执行时,组件实例还没被创建!
- beforeRouteUpdate:在当前路由改变,但是该组件被复用时调用。举例:对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。注意:因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
- beforeRouteLeave:在导航离开渲染该组件的对应路由时调用,与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
4.2 路由元信息
有时可能需要将某些信息附加到路由上,如过渡名称、谁可以访问路由等。此时,可以通过路由信息对象的 meta
属性实现。定义路由时配置 meta
字段:
const routes = [{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
meta: {requiresAuth: true},// 只有经过身份验证的用户才能创建帖子
},
{
path: ':id',
component: PostsDetail
meta: {requiresAuth: false},// 任何人都可以阅读文章
}
]}]
那么访问这个属性呢?
前置概念:routes
配置中的每个路由对象都被称为 路由记录 。路由记录 可以是嵌套 的,因此,当一个路由匹配成功后,它可能匹配多个路由记录。
例如,根据上面的路由配置,/posts/new
这个 URL 将会匹配父路由记录 (path: '/posts'
) 以及子路由记录 (path: 'new'
)。
一个路由匹配到的所有路由记录会暴露为 route
对象的 route.matched
数组。 所以我们可以通过遍历这个数组来检查路由记录中的 meta
字段,但是 Vue Router 还提供了一个 route.meta
方法,它是一个非递归合并 所有 meta
字段(从父字段到子字段)的方法。
示例:为某些路由设置登录权限
router.beforeEach((to, from) => {// 使用数组的 some()方法检查每条路由记录
to.matched.some(record => record.meta.requiresAuth)
// 使用 route.meta 方法
if (to.meta.requiresAuth && !auth.isLoggedIn()) {
// 此路由需要授权,请检查是否已登录,如果没有,则重定向到登录页面
return {
path: '/login',
query: {redirect: to.fullPath},// 保存我们所在的位置,以便以后再来
}
}})
4.3RouterView 插槽
RotuerView 组件暴露了一个插槽,可以用来渲染路由组件:
上面的代码等价于不带插槽的
,但是当我们想要获得其他功能时,插槽提供了额外的扩展性。
KeepAlive & Transition
当在处理 KeepAlive 组件时,我们通常想要保持路由组件活跃,而不是 RouterView 本身。为了实现这个目的,我们可以将 KeepAlive 组件放置在插槽内:
类似地,插槽允许我们使用一个 Transition 组件来实现在路由组件之间切换时实现过渡效果
更多 RouterView 插槽使用,参考:RouterView 插槽 | Vue Router (vuejs.org)
若有错误或描述不当的地方,烦请评论或私信指正,万分感谢 😃
原文地址: Vue 路由:Vue router