前端路由
问题导入
在前面完成的资产管理案例中, 我们是把列表区域和添加表单区域实现在了一个区域。当页面功能比较复杂时,我们需要它们拆分开来:一个页面中只显示一个区域。
一个比较直观的解决方案是把它们分别做成两个独立的网页文件,例如:
文件一: xxxx/index.html 显示表格区区域
文件二:xxxx/add.html显示表单区域
然后添加一个导航条来允许用户进行跳转。
这种解决方案比较直接了当,但它存在一些问题:
- 从一个页面跳入另一个页面需要重新加载公共的资源文件,造成浪费。例如index.html中需要用到axios.js,在add.html中也需要用到。
- 页面的跳入跳出给用户的体验也不好(特别地,是在移动端)。
那有没有一种方案在不进行页面跳转的前提下,能根据地址栏中的地址不同,来显示不同的内容?
有,这就是前端路由技术。
前端路由:
- 根据地址栏变化(不发请求),去局部更新不同的页面内容。
- 前端业务场景切换。
模拟实现原理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<nav>
<!-- #:在地址栏有两个含义
- 锚点链接:在页面内部进行导航,不会有页面跳转。
- hash。在前端路由技术中,我们叫它哈希值。它不会有页面跳转,地址栏变化的
浏览器中后退,前进也是可用的
http://127.0.0.1:5500/index.html#/add -->
<a href="#/">主页</a>
<a href="#/add">添加</a>
</nav>
<div id="content">
<!-- // 模拟不同的地址栏中的hash值,显示不同的内容 -->
</div>
</div>
<script>
// 实现前端路由效果
// - 地址栏变化,页面内容变化,不刷新
// 1. 如何监听地址栏中hash值的变化?
// 答:在window上,添加hashchange监听
// 2. 如何获取当前地址栏中的hash值?
// 答:location.hash
window.addEventListener('hashchange', onHashChange)
// 当页面中的dom加载完成,就去执行
window.addEventListener('DOMContentLoaded',function() {
onHashChange()
})
function onHashChange (e) {
console.log('hash值的变了')
console.log(e)
// 根据不同的hash值,显示不同的内容到content区域
switch (location.hash) {
case "#/":
// 主页
document.getElementById('content').innerHTML = "这里主页的内容"
break;
case "#/add":
// add
document.getElementById('content').innerHTML = "这里是add的内容"
break;
default:
document.getElementById('content').innerHTML = "404没有这个页面"
break;
}
// if elseif ,elseif ......
}
</script>
</body>
</html>
技术要点:
- 地址url 中看到 #,这个 # 有两种情况,一个是我们所谓的锚点,比如典型的回到顶部按钮原理、Github 上各个标题之间的跳转等,路由里的 # 不叫锚点,我们称之为 hash。地址栏中hash的变化是不会发生页面跳转的
- hashchange 事件用来监听hash值的变化。
- hash的改变也会记录到浏览历史中,通过回退和前进可以切换业务场景
SPA
单页面应用程序,简称SPA(single page application)一个系统上的所有功能在一个页面上实现。
(在移动端比较多)
SPA是通过前端路由实现业务场景的切换的。
在vue框架中额外引入vue-router插件来配合组件系统就可以轻易地实现。
在单页面应用程序中,如何切换页面的业务场景。
- http://zhoushugang.gitee.io/hm-toutiao-pc-93/
- https://music.163.com/
优点:
- 整体不刷新页面,用户体验更好。
缺点:
- 首次加载会比较慢一点。
vue-router-使用步骤
vue-router是vue的一个插件,当我们的项目需要前端路由时,我们要先把它下载引入到页面中。
下载: https://cdn.bootcss.com/vue-router/3.1.3/vue-router.min.js
导入插件
# 先下载到本地,再引用
<script src="./vue-router.min.js"></script>
# 直接引用
<script src="https://cdn.bootcss.com/vue-router/3.1.3/vue-router.min.js"></script>
初始化vue-router插件
用VueRouter构造器创建路由实例,并配置路由规则。
基本格式
const router = new VueRouter({
routes: [
{path:"路径1",component:要展示的组件1},
{path:"路径2",component:要展示的组件2},
.....
]
})
示例
- vue-router中有一套约定的规则用来确定在哪个url下显示哪个组件。
// 初始化vue-router且使用刚定义的路由规则
const router = new VueRouter({
// 初始化路由的配置对象
// 有以一个配置项 routes 定义路由规则
routes:[
{path: '/', component: {template:`<div>我是主页</div>`}},
{path: '/news', component: {template:`<div>新闻-生活早知道</div>`}},
{path: '/sport', component: {template:`<div>体育-体育改变人生</div>`} }
]
})
使用路由实例
在vue构造器中,有一项是router,它专门用来设置路由对象
new Vue({
el: '#app',
// vue提供了一个配置选项,router选项,是用来挂载路由实例的
// 只有挂载了 router 实例 才可使用路由的功能
router:路由对象
})
设置路由出口
在vue的模板,添加一个router-view组件,用它来指定当前路由对应组件渲染的位置。
<!-- 渲染路由对应的组件 router-view承载路由对应的组件的-->
<router-view></router-view>
测试使用
请直接在地址栏中补充对应的路由地址来查看路由效果。
路由链接导航
通过router-link来进行路由跳转。
<!-- 写路由链接 不会使用a标签 使用router-link组件 -->
<nav>
<!-- 组件默认解析的是a标签 to属性跳转的地址,不需要带上# -->
<router-link to="/">主页</router-link>
<router-link to="/news">新闻</router-link>
<router-link to="/sports">运动</router-link>
</nav>
- router-link组件会被vue解析成a标签,但不能直接通过a标签来跳转。
- 如果当前路由被激活会添加特殊的类名:
router-link-exact-active router-link-active
动态路由
目标:实现新闻详情的功能:即不同的新闻使用同一个组件,但要传入不同的参数。
概念:不同的路由地址,指向同一个组件,此时需要使用动态路由。
示图:
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.router-link-exact-active{
color:red;
padding:0 5px;
}
</style>
</head>
<body>
<div id="app">
<h3>动态路由</h3>
<router-link to="/detail/100">编号为100的新闻</router-link>
<router-link to="/detail/101">编号为101的新闻</router-link>
<router-link to="/detail/102">编号为102的新闻</router-link>
<router-link to="/detail/103">编号为103的新闻</router-link>
<router-view></router-view>
</div>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script>
// 动态路由
// -- 同一个组件,根据传入参数不同,显示不同的内容。
// 例如:大事件项目前中的新闻详情页。
// localhost:3000/index.html#/detail/100 ----> 显示编号为100的新闻
// localhost:3000/index.html#/detail/101 ----> 显示编号为101的新闻
// 1. 定义路由规则
// path:'/detail/:形参名'
// 2. 跳转 `to:/detail/实参`
// 3. 在组件中获取当前的参数值?
// this.$route.params.形参
// - $route 引入了vueRouter插件之后, vue组件内部通过 $route 就可以直接来访问当前的组件对象
// - $route这个对象有很多属性,params用来保存当前传入动态路由的参数值
const myrouter = new VueRouter({
routes: [
// path是hash值,component是组件,就是显示的内容
{path: '/',component:{template: `<div>我是主页!!!!!</div>`}},
// /detail/:id 可以匹配类似于: /detail/XXXXX 的路径
{path:'/detail/:id123',component:{
template: `<div>
<h1>新闻详情页-{{$route.params.id123}}</h1>
<button @click="h1">点击</button>
</div>`,
methods: {
h1 () {
console.log( this.$route.params.id123)
}
}
}
},
{path: '*',component:{template: `<div>你要访问的页面不存在,404!!!!</div>`}},
]
})
new Vue({
el: '#app',
data:{},
methods:{},
router: myrouter// 用来设置路由对象
})
</script>
</body>
</html>
总结:
- 在路由规则中,匹配到不同的地址,指向同一个组件
- 代码:
{path:'/detail/:id', component: detail}
- 数据:模板
{{$route.params.id}}
组件this.$route.params.id
页面跳转及传参
从页面pageA跳转到pageB ,并携带参数
跳转有两种方式:
- 通过router-link的to属性跳转
- 通过$router.push()方法来跳转
传参有两种方式:
- 查询传参
/aricle?id=1001
- 路径传参
/article/1001
获取传参数的方式: (根据传参不同,获取参数也不同)
- 查询传参: this.$route.query.id
- 路径传参:this.$route.params.id
声明式导航
通过<router-link>
组件的属性to来声明它的跳转后的路由地址。
不带参数的跳转
- 普通字符串,不带参数。
<router-link to="/list"></router-link>
- 对象,普通跳转,不带参数
<router-link :to="{path:'/list'}"></router-link>
带参数的跳转
- 普通字符串,进行带参数的跳转。
<!-- 路径传参 路由规则{path:'/article/:id'}-->
<router-link to="/article/10001"></router-link>
<!-- 查询传参 路由规则{path:'/article'}-->
<router-link to="/article?id=10001"></router-link>
- 对象,路径传参
<!--路径传参 路由规则{path:'/article/:id',name:'article',component:Article}] --->
<router-link :to="{name:'article',params:{id: 10001}}"></router-link>
<!-- /article/10001 -->
- 对象,查询传参
<!--路径传参 路由规则 {path:'/article',component:ArticleItem} -->
<router-link :to="{path:'/article',query:{id: 10001}}"></router-link>
<!-- /article?id=10001 -->
代码:
<div id="app">
<!-- 各种router-link写法 -->
<!-- 字符串 -->
<router-link to="/list">文章列表</router-link>
<router-link to="/article/10001">文章详情</router-link>
<router-link to="/item?id=10001">文章详情</router-link>
<hr>
<!-- 对象 -->
<router-link :to="{path:'/list'}">文章列表</router-link>
<router-link :to="{name:'article',params:{id:10001}}">文章列表</router-link>
<router-link :to="{path:'/item',query:{id:10001}}">文章列表</router-link>
<!-- 显示路由对应组件容器 -->
<router-view></router-view>
</div>
<script src="./vue.js"></script>
<script src="./vue-router.min.js"></script>
<script>
// routes 指定路由规则数组
const router = new VueRouter({
routes:[
{
path: '/list',
component: {
template: `<div>列表组件</div>`
}
},
{
path: '/article/:id',
name: 'article',
component: {
template: `<div>article文章详情组件 {{$route.params.id}}</div>`
}
},
{
path: '/item',
component: {
template: `<div>item文章详情组件 {{$route.query.id}}</div>`
}
}
]
})
new Vue({
el: '#app',
router
})
</script>
总结:对象方式的两种传参,怎么取值。
:to="{name:'article',params:{id:10001}}"
$route.params.id- 解析后:/article/10001
:to="{path:'/item',query:{id:10001}}"
$route.query.id- 解析后:/item?id=10001
编程式导航
通过 js代码调用一个导航函数(this.$router.push)进行跳转。
场景:
- 在界面上,有确切的跳转链接,使用声明式导航。
- 当你在执行一个js逻辑的时候,你想进行跳转,此时使用编程式导航。
- 在做登录的时候,登录成功后,才应该跳转到首页。
格式:
// 字符串
this.$router.push('/home')
// 对象
this.$router.push({ path: 'home' })
// 命名的路由
// 路径传参
this.$router.push({ name: 'user', params: { userId: '123' }})
// 查询参数,变成 /register?plan=private
this.$router.push({ path: 'register', query: { plan: 'private' }})
代码:
<div id="app">
<!-- 声明式导航 -->
<router-link to="/login">登录页面</router-link>
<!-- 显示路由对应组件容器 -->
<router-view></router-view>
</div>
<script src="./vue.js"></script>
<script src="./vue-router.min.js"></script>
<script>
const router = new VueRouter({
// routes 指定路由规则数组
routes:[
{
path: '/login',
component: {
template: `<div>
<input type="text" placeholder="用户名">
<input type="password" placeholder="密码">
<button @click="login">登录</button>
</div>`,
methods: {
login () {
// 进行登录
// 假设登录成功了
// 跳转到首页
// 只能通过js的方式进行跳转(编程式导航)
// vue实例提供了一个对象 $router
// $router就是路由实例,提供一个函数 push,可以进行跳转
this.$router.push('/home')
}
}
}
},
{
path: '/home',
component: {
template: `<div>首页</div>`
}
}
]
})
new Vue({
el: '#app',
router
})
</script>
总结:
- js代码执行的导航跳转就是编程式导航
this.$router.push('/home')
- to属性解析过执行的跳转,依赖的代码其实就是编程式导航代码。
- router-link的to属性能使用的传参方式,在编程式导航中都可以使用。
// 路径传参
this.$router.push({name:'article',params:{id:10001}})
// 键值对传参
this.$router.push({path:'/item',query:{id:10001}})
路由重定向
重定向:
- 当你访问某个地址的时候,经过程序的处理(用户看不见),跳转到了另外一个地址。
前端的路由,使用使用重定向功能,假设一个业务场景:
- 当你访问页面的时候,默认hash地址是
#/
,默认的路由地址/
- 此时我们项目的首页
/home
,所以:当我们访问/
重定向到/home
,才能默认访问首页。
代码:
<!-- 根容器 -->
<div id="app">
<router-view></router-view>
</div>
<script src="./vue.js"></script>
<script src="./vue-router.min.js"></script>
<script>
const router = new VueRouter({
// routes 指定路由规则数组
routes:[
// 匹配 / 路径,重定向 到 '/home' 即可
{
path: '/', redirect: '/home'
},
// 首页路由规则
{
path: '/home',
component: {
template: '<div>首页页面内容</div>'
}
}
]
})
new Vue({
el: '#app',
router
})
</script>
总结:
- 路由规则对象中 提供了一个选项:redirect 配置重定向的地址即可。
路由嵌套
原理:router-view中再次包含router-view。
背景:一个组件内部包含的业务还是很复杂,需要再次进行拆分。
格式:
routes:[
{
path: '/sport',
component: {template:`<div><router-view></router-view></div>`},
children: [
path:'/xx1',
]
}
]
示例
总结:
- 在已有的路由容器中,再实现一套路由,再套一个路由容器,叫:嵌套路由。
代码:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
background-color: #eee;
}
nav {
border:1px solid #ccc;
padding:1em;
margin:5px;
}
#app {
background-color: #fff;
width: 500px;
margin: 50px auto;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
padding: 2em;
}
.box {
padding: 1em;
border: 1px solid #ccc;
margin: 1em;
}
.router-link-active{
}
.router-link-exact-active {
color:red;
}
</style>
</head>
<body>
<div id="app">
<nav>
<!-- 通过router-link来跳转页面
如果是当前页,则会添加 .router-link-exact-active ,.router-link-active 类 -->
<router-link to="/">主页</router-link>
<router-link to="/news">新闻</router-link>
<router-link to="/sport">体育</router-link>
</nav>
<!-- 一级路由容器 -->
<router-view></router-view>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/vue-router/3.1.3/vue-router.js"></script>
<script>
const sports = {
template:`
<div>
<h3>体育-体育改变人生</h3>
<router-link to="/sport">主页</router-link>
<router-link to="/sport/guonei">国内</router-link>
<router-link to="/sport/guowai">国外</router-link>
<router-view></router-view>
</div>
`
}
// 初始化vue-router且使用刚定义的路由规则
const router = new VueRouter({
// 初始化路由的配置对象
// 有以一个配置项 routes 定义路由规则
routes:[
{path: '/', component: {template:`<div>我是主页</div>`}},
{path: '/news', component: {template:`<div>新闻-生活早知道</div>`}},
{path: '/sport',
component: sports,
children: [
{
path: '', // 二级路由的默认显示内容
component: {template:'<div><h4>体育栏目的主页</h4></div>'}
},
{
path: 'guonei',
component: {template:'<div><h4>国内体育新闻</h4></div>'}
},
{
path: 'guowai',
component: {template:'<div><h4>国外体育新闻</h4></div>'}
}
]
},
{path: '/detail/:id', component: {template:`<div>我是新闻详情页{{$route.params.id}}</div>`}}
]
})
new Vue({
el: '#app',
router
})
</script>
</body>
</html>
总结:
-
嵌套路由除了 router-view 之间需要嵌套,路由规则也需要通过children来实现嵌套。
children: [ { path: '', // 二级路由的默认显示内容 component: {template:'<div><h4>体育栏目的主页</h4></div>'} }, { path: 'guonei', component: {template:'<div><h4>国内体育新闻</h4></div>'} }, { path: 'guowai', component: {template:'<div><h4>国外体育新闻</h4></div>'} } ] }, {path: '/detail/:id', component: {template:`<div>我是新闻详情页{{$route.params.id}}</div>`}} ]
})
new Vue({
el: ‘#app’,
router
})
总结:
- 嵌套路由除了 router-view 之间需要嵌套,路由规则也需要通过children来实现嵌套。