# 前端路由概念与原理
# 什么是路由
路由(英文:router)就是对应关系
# SPA 与前端路由
SPA 指的是一个 web 网站只有唯一的一个 HTML 页面,所有组件的展示与切换都在这唯一的一个页面内完成。此时,不同组件之间的切换需要通过前端路由来实现
# 前端路由定义
Hash 地址与组件之间的对应关系
# 前端路由工作方式
- ① 用户点击了页面上的路由链接
- ② 导致了 URL 地址栏中的 Hash 值发生了变化
- ③ 前端路由监听了到 Hash 地址的变化
- ④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中
# 实现简易的前端路由
# 步骤一
通过 <component> 标签,结合 comName 动态渲染组件
<template> | |
<div> | |
<component :is="comName"></component> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: 'APP', | |
data () { | |
return { | |
comName: 'Home', | |
} | |
} | |
} | |
</script> |
# 步骤二
在 App.vue 组件中,为 <a> 链接添加对应的 hash 值
<template> | |
<div> | |
<a href="#/home">Home</a> | |
<a href="#/movie">Movie</a> | |
<a href="#/about">About</a> | |
<component :is="comName"></component> | |
</div> | |
</template> |
# 步骤三
在 created 生命周期函数中,监听浏览器地址栏中 hash 地址的变化,动态切换要展示的组件的名称
<script> | |
export default { | |
name: 'APP', | |
data () { | |
return { | |
comName: 'Home', | |
} | |
}, | |
created () { | |
window.onhashchange = () => { | |
switch (location.hash) { | |
case: '#/home': | |
this.comName = 'Home' | |
break | |
case: '#/movie': | |
this.comName = 'Movie' | |
break | |
case: '#/about': | |
this.comName = 'About' | |
break | |
} | |
} | |
} | |
} | |
</script> |
# vue-router 的基本使用
# vue-router 定义
vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换
# vue-router 安装和配置的步骤
- ① 安装 vue-router 包
- ② 创建路由模块
- ③ 导入并挂载路由模块
- ④ 声明路由链接和占位符
# 在项目中安装 vue-router
npm i vue-router@3.5.2 -S
# 创建路由模块
在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码
import Vue from 'vue' | |
import VueRouter from 'vue-router' | |
// 调用 Vue.use () 函数,把 VueRouter 安装为 Vue 插件 | |
Vue.use(VueRouter) | |
// 创建路由实例对象 | |
const router = new VueRouter() | |
// 向外共享路由实例对象 | |
export default router |
# 导入并挂载路由模块
在 src/main.js 入口文件中,导入并挂载路由模块
import Vue from 'vue' | |
import App from './App.vue' | |
// 导入路由模块 默认访问 index.js | |
import router from '@/router' | |
new Vue({ | |
render: h => h(App), | |
// 挂载路由模块 | |
router: router | |
}).$mount('#app') |
# 生命路由连接和占位符
在 src/App.vue 组件中,使用 vue-router 提供的 <router-link> 和 <router-view> 声明路由链接和占位符
<template> | |
<div> | |
<!-- 定义路由连接 --> | |
<router-link to="/home">首页</router-link> | |
<router-link to="/movie">电影</router-link> | |
<router-link to="/about">关于</router-link> | |
<!-- 定义路由占位符 --> | |
<router-view></router-view> | |
</div> | |
</template> |
# 声明路由匹配规则
import Vue from 'vue' | |
import VueRouter from 'vue-router' | |
import Home from '@/components/Home.vue' | |
import Movie from '@/components/Movie.vue' | |
import About from '@/components/About.vue' | |
// 调用 Vue.use () 函数,把 VueRouter 安装为 Vue 插件 | |
Vue.use(VueRouter) | |
// 创建路由实例对象 | |
const router = new VueRouter({ | |
routes: [ | |
{ path: '/home', component: Home}, | |
{ path: '/movie', component: Movie}, | |
{ path: '/about', component: About} | |
] | |
}) | |
// 向外共享路由实例对象 | |
export default router |
# vue-router 常用用法
# 路由重定向
路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面
const router = new VueRouter({ | |
routes: [ | |
{ path: '/', redirect: '/home'}, | |
{ path: '/home', component: Home}, | |
{ path: '/movie', component: Movie}, | |
{ path: '/about', component: About} | |
] | |
}) |
# 嵌套路由
通过路由实现组件的嵌套展示
# 声明子路由连接和子路由占位符
在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符
<template> | |
<div> | |
<router-link to="/about/tab1">tab1</router-link> | |
<router-link to="/about/tab2">tab2</router-link> | |
<router-view></router-view> | |
</div> | |
</template> |
# 通过 children 属性声明子路由规则
在 src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则
import Tab1 from '@/components/tabs/Tab1.vue' | |
import Tab2 from '@/components/tabs/Tab2.vue' | |
const router = new VueRouter({ | |
routes: [ | |
{ | |
path: '/about', | |
component: About, | |
children: [ | |
{ path: 'tab1', component: Tab1}, | |
{ path: 'tab2', component: Tab2} | |
] | |
} | |
] | |
}) |
# 动态路由配置
# 动态路由概念
动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性
const router = new VueRouter({ | |
routes: [ | |
{ path: '/movie/:id', component: Movie} | |
] | |
}) |
# $route.params 参数对象
在动态路由渲染出来的组件中,可以使用 this.$route.params 对象访问到动态匹配的参数值
# 使用 props 接收路由参数
为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参
const router = new VueRouter({ | |
routes: [ | |
{ path: '/movie/:id', component: Movie, props: true} | |
] | |
}) |
# 声明式导航 & 编程式导航
在浏览器中,点击链接实现导航的方式,叫做声明式导航,普通网页中点击 <a> 链接、vue 项目中点击 <router-link> 都属于声明式导航
在浏览器中,调用 API 方法实现导航的方式,叫做编程式导航,普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
# 4.1 vue-router 中编程式导航 API
- ① this.$router.push ('hash 地址 ')
- 跳转到指定 hash 地址,并增加一条历史记录
- ② this.$router.replace ('hash 地址 ')
- 跳转到指定的 hash 地址,并替换掉当前的历史记录
- ③ this.$router.go (数值 n)
- 实现导航历史前进、后退
# $router.push
调用 this.$router.push () 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面
<template> | |
<div> | |
<h1>Home 组件</h1> | |
<button @click="gotoMovie">跳转 Movie 组件</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
methods: { | |
gotoMovie () { | |
this.$router.push('/movie/1') | |
} | |
} | |
} | |
</script> |
# $router.replace
调用 this.$router.replace () 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面
push 和 replace 的区别:
- push 会增加一条历史记录
- replace 不会增加历史记录,而是替换掉当前的历史记录
# $router.go
调用 this.$router.go () 方法,可以在浏览历史中前进和后退
<template> | |
<div> | |
<h1>Home 组件</h1> | |
<button @click="goBack">goBack</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
methods: { | |
goBack () { | |
this.$router.go(-1) | |
} | |
} | |
} | |
</script> |
# $router.go 的简化用法
在实际开发中,一般只会前进和后退一层页面。因此 vue-router 提供了如下两个便捷方法
- $router.back()
- 在历史记录中,后退到上一个页面
- $router.forward()
- 在历史记录中,前进到下一个页面
# 导航守卫
# 全局前置守卫
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行访问权限的控制
const router = new VueRouter({}) | |
router.beforeEach(fn) |
# 守卫方法的 3 个参数
const router = new VueRouter({}) | |
router.beforeEach((to, from, next) => { | |
//to 式将要访问的路由信息对象 | |
//from 式要离开的路由信息对象 | |
//next 式一个函数,调用 next () 表示放行,允许这次路由导航 | |
}) |
# next 函数的 3 种调用方式
- 当前用户拥有后台主页的访问权限,直接放行:next ()
- 当前用户没有后台主页的访问权限,强制其跳转到登录页面:next ('/login')
- 当前用户没有后台主页的访问权限,不允许跳转到后台主页:next (false)
# 控制后台主页的访问权限
router.beforeEach((to, from, next) => { | |
if (to.path === '/main') { | |
const token = localStorage.getItem('token') | |
if (token) { | |
next() | |
} else { | |
next('/login') | |
} | |
} else { | |
n | |
} | |
}) |