笔记来源:拉勾教育 - 大前端就业集训营
文章内容:学习过程中的笔记、感悟、和经验
Vue Router - 路由
是Vue的官方插件,用来快速实现单页应用
单页应用程序 - SPA
单页应用:网站的 “所有” (很多)功能,都呈现在单个页面中,例如:后台管理系统、移动端、小程序都适合使用单页应用开发
优点
- 前后端分离开发,提高开发效率
- 业务场景切换,局部更新结构
- 用户体验好,接近本地应用
缺点
- 不利于SEO
- 初次首屏加载较慢
- 页面复杂度较高
前端路由
指的是Url与内容之间的映射关系,下面是前端路由的两种实现方式
Hash方式
通过监听hashchange事件监听hash(Url的组成部分)变化,并进行页面内容更新的方式
修改hash既能更改url又不会引发页面跳转,特点就是Url中会存在一个#
<body>
<!-- 挂载元素 -->
<div id="app">
<!-- 三个a标签用来点击切换hash -->
<a href="#/">./</a>
<a href="#/one">./one</a>
<a href="#/tow">./tow</a>
<!-- p标签绑定str -->
<p id="me">还没点击</p>
</div>
<script>
window.onhashchange = function () {
// 过滤hash并却掉#
const hash = location.hash.replace('#', '')
// 创建字符串
let str = ''
// 把hash带入循环,根据值的不同修改str
switch (hash) {
case '/':
str = '我点击了./'
break;
case '/one':
str = '我点击了./one'
break;
case '/tow':
str = '我点击了./tow'
break;
}
// 把str设置给p标签
document.getElementById('me').innerText = str
}
</script>
</body>
<!-- 这里只演示hash变化可以更改内容,只演示功能 -->
封装上面的方法
<body>
<!-- 挂载元素 -->
<div id="app">
<!-- 三个a标签用来点击切换hash -->
<a href="#/">./</a>
<a href="#/one">./one</a>
<a href="#/tow">./tow</a>
<!-- p标签绑定str -->
<p id="me">还没点击</p>
</div>
<script>
// 创建对象封装路由功能
const router = {
// 路由存储位置:用来存储 url-hash 和处理函数的对应关系
routes: {},
// 用于添加路由规则
route: function (path, callback) {
// 把hash和callback的对应关系存在routes里面,即每一个hash的数值都对应着一个处理函数
this.routes[path] = callback
},
// 初始化对象方法
init: function () {
// 获取当前对象存储起来
const that = this
// 创建监听hash变化
window.onhashchange = function () {
// 获取处理url的hash 并却掉#
const hash = location.hash.replace('#', '')
// 判断routes里面是否有这个hash值,如果有调用相应函数
that.routes[hash] && that.routes[hash]()
}
console.log('开启监听')
}
}
// 初始化路由
router.init()
// 获取标签元素
const me = document.getElementById('me')
// 添加路由规则
router.route('/', function () {
me.innerText = '我点击了./'
})
router.route('/one', function () {
me.innerText = '我点击了./one'
})
router.route('/tow', function () {
me.innerText = '我点击了./tow'
})
console.log(router)
</script>
</body>
hash方式特点
- 兼容性好,传统方式
- 地址中有#,不太美观
- 前进后退功能比较繁琐
History方式
采用HTML5提供的新功能实现前端路由,兼容性没有上一个好
使用HTML5中提供的history.pushState()变更URL并执行相应操作
<body>
<!-- 三个a标签 -->
<a href="/">/</a>
<a href="/one">one</a>
<a href="/tow">tow</a>
<!-- 内容展示区 -->
<p id="me">默认内容</p>
<script>
// 创建路由对象
const router = {
// 存储路由对应规则
routers: {},
// 输出路由和对应函数,添加到routers
route(path, callback) {
this.routers[path] = callback
},
// 执行路由
go(path) {
// 使用history.pushState方法变更url
// 第一个参数是当前路径有关的数据,暂时没有先写null
// 第二个参数现在浏览器不支持,写null
// 第三个参数是路由名
history.pushState(null, null, path)
// 判断路由是否存在规则并执行路由规则中的函数
this.routers[path] && this.routers[path]()
}
}
// 获取元素
const as = document.getElementsByTagName('a')
const me = document.getElementById('me')
// 遍历全部的a标签添加click事件
for (const a of as) {
a.onclick = function (e) {
// 获取点击的a标签的href内容,这里不能直接使用e.target.href,因为这样会去获取到整个url,而我们只需要href
const href = e.target.getAttribute('href')
// 执行路由功能跳转
router.go(href)
// 阻止a默认跳转
return false
}
}
// 添加路由规则
router.route('/', function () {
me.innerText = './的内容'
})
router.route('/one', function () {
me.innerText = './one的内容'
})
router.route('/tow', function () {
me.innerText = './tow的内容'
})
// 总结:原理上来睡,就是创建一个对象存储路由相关的功能,把每一个路由名和对应的相关函数规则一一对应,最后当我们更改路由名的时候执行相呼应的相关操作
</script>
</body>
问题:虽然我们实现了点击路由更新数据,但是我们发现点击浏览器前进、后退按钮页面并不会根据路由发生变化
解决方法:
-
前进后退功能,需要在更改URL的时候保存路由标记
-
// 在更改url的时候添加标记,后面可以给前进后退功能使用 history.pushState({'path': path}, null, path)
-
-
通过popstate(window)事件监听前进后退操作,检测state
-
// 初始化方法 init() { // 存储一下this const that = this // 监听前进后退事件popstate window.onpopstate = function (e) { console.log(e) // 获取标记, 标记存在e.state.path中, 先判断一下有没有, 没有使用/ const path = e.state ? e.state.path : '/' // 调用路由相关的函数 that.routers[path] && that.routers[path]() } }
-
-
调用初始化方法添加事件监听
-
// 执行路由初始化操作监听前进后退 router.init()
-
完整代码
<body>
<!-- 三个a标签 -->
<a href="/">/</a>
<a href="/one">one</a>
<a href="/tow">tow</a>
<!-- 内容展示区 -->
<p id="me">默认内容</p>
<script>
// 创建路由对象
const router = {
// 存储路由对应规则
routers: {},
// 输出路由和对应函数,添加到routers
route(path, callback) {
this.routers[path] = callback
},
// 执行路由
go(path) {
// 使用history.pushState方法变更url
// 第一个参数:添加path标记,把每次的路由存进来,后面前进后退可以使用
// 第二个参数现在浏览器不支持,写null
// 第三个参数是路由名
history.pushState({}, null, path)
// 判断路由是否存在规则并执行路由规则中的函数
this.routers[path] && this.routers[path]()
},
// 初始化方法
init() {
// 存储一下this
const that = this
// 监听前进后退事件popstate
window.onpopstate = function (e) {
console.log(e)
// 获取标记, 标记存在e.state.path中, 先判断一下有没有, 没有使用/
const path = e.state ? e.state.path : '/'
// 调用路由相关的函数
that.routers[path] && that.routers[path]()
}
}
}
// 执行路由初始化操作监听前进后退
router.init()
// 获取元素
const as = document.getElementsByTagName('a')
const me = document.getElementById('me')
// 遍历全部的a标签添加事件
for (const a of as) {
a.onclick = function (e) {
// 获取点击的a标签的href内容
const href = e.target.getAttribute('href')
// 执行路由功能跳转
router.go(href)
// 阻止a默认跳转
return false
}
}
// 添加路由规则
router.route('/', function () {
me.innerText = './的内容'
})
router.route('/one', function () {
me.innerText = './one的内容'
})
router.route('/tow', function () {
me.innerText = './tow的内容'
})
// 总结:原理上来睡,就是创建一个对象存储路由相关的功能,把每一个路由名和对应的相关函数规则一一对应,最后当我们更改路由名的时候执行相呼应的相关操作
</script>
</body>
特点
- 兼容性较差 - ie10+支持,但是往后会被普及开来,刷新可能会出现一些问题
- 书写简便,格式美观,可存储数据量大
Vue router
vue官方的路由管理器,让构建单页面应用易如反掌
基本使用
**安装:**注意要先引入vue再引入Vue router
-
直接下载、CDN引入
-
npm安装:
npm i vue-router
Vue router提供了一个全局VueRouter构造函数,并且提供了用于路由设置的组件<router-link>和<router-view>
- router-link:用于切换的连接,功能类似于之前的a标签,默认是a标签,可以通过tag设置为其他标签
- router-view:路由切换之后要进行内容切换的区域,使用router-view设置,用来显示路由匹配到的组件
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入vue和vue router -->
<script src="./js/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<!-- router点击列表,这里可以使用tag修改默认组件标签 -->
<router-link to="/" tag="button">index</router-link>
<router-link to="/one">one</router-link>
<router-link to="/tow">tow</router-link>
<!-- router视图部分,用于展示router-link对应组件 -->
<router-view></router-view>
</div>
<script>
// 创建三个组件,这三个组件不需要添加到vue实例router就可以使用
const index = {
template: `<div>index的内容</div>`
},
one = {
template: `<div>one的内容</div>`
},
tow = {
template: `<div>tow的内容</div>`
}
// 创建VueRouter实例
const router = new VueRouter({
// VueRouter组件对应规则
routes: [{
path: '/', // 路由
component: index //路由对应组件
},
{
path: '/one',
component: one
},
{
path: '/tow',
component: tow
}
]
})
// vue实例
const vm = new Vue({
el: '#app',
// 把router注入vue实例,这里采用es6语法
router
})
// 注意:如果我们不注入router则vue中就没有router相关方法,注入后打印vue就可以看到router,
</script>
</body>
步骤解析,以下步骤不分先后
- 顶部vue挂载对象内部书写router-link和router-view
- 创建需要使用的组件,组件可以不引入vue也可以使用
- 创建vue-router实例,在实例中的touters中创建路由和组件的对应规则
- 创建vue实例,将router实例注入实例中
命名视图
如果希望导航后,同时显示多个视图组件(router-view),这时就需要进行命名视图设置
注意当多个router-view存在的时候,只能有一个没有name属性,默认名称default
<body>
<div id="app">
<!-- 路由可点击列表 -->
<router-link to="/one">one</router-link>
<router-link to="/tow">tow</router-link>
<!-- router视图部分!!!!!!!!!!!!!!!!! -->
<!-- 新建一个视图,并命名为index!!!!!!!!!!!!!!!!!!!!! -->
<router-view name="index"></router-view>
<router-view></router-view>
</div>
<script>
// 创建三个组件,这三个组件不需要添加到vue实例router就可以使用
const index = {
template: `<div>index的内容,我是公共组件</div>`
},
one = {
template: `<div>one的内容</div>`
},
tow = {
template: `<div>tow的内容</div>`
}
// 创建VueRouter实例
const router = new VueRouter({
// VueRouter组件对应规则
routes: [{
path: '/one',
// 如果想让一个路由同时设置多个视图,把components设置为对象!!!!!!!!!!!!!!!
components: {
// 视图name:组件名,这里都是index简写
index,
// 没有命名的视图默认名字是default
default: one
}
},
{
path: '/tow',
components: {
index,
default: tow
}
}
]
})
// vue实例
const vm = new Vue({
el: '#app',
// 把router注入vue实例,这里采用es6语法
router
})
// 注意:如果我们不注入router则vue中就没有router相关方法,注入后打印vue就可以看到router,
</script>
</body>
解析:一个路由可以同时更新多个router-view视图,但要注意多个view不能同名,默认情况需那个下view默认名为default,可以使用name属性更给名字
动态路由
当我们需要将某一类URL都映射到同一个组件,就需要使用动态路由
使用方法
- 定义路由规则的时候,将路径-path中的某一部分使用:进行标记,即可设置动态路由
- 设置动态路由后,动态部分可以是任意内容
- 设置动态路由后,动态部分为任意内容均跳转到同一个组件
- :部分对应的信息成为路径参数,存储在
vue实例.$route.params中
<body>
<div id="app">
<!-- 写三个link,三个组件映射到同一个路由 -->
<router-link to="/user/1">用户1</router-link>
<router-link to="/user/2">用户2</router-link>
<router-link to="/user/3">用户3</router-link>
<!-- 视图 -->
<router-view></router-view>
</div>
<script>
// 使用的组件
const user = {
template: `<div>用户{{$route.params.id}}的功能</div>`
}
// 路由实例
const router = new VueRouter({
// 路由映射规则
routes: [{
// :id部分是动态的,会根据上面router-link进行设置
path: '/user/:id',
component: user
}]
})
// vue实例
const vm = new Vue({
// 挂载元素
el: '#app',
// 注入路由
router
})
</script>
</body>
侦听路由参数
动态路由切换时,不是组件的销毁和创建,而是不断复用组件,这样就会导致生命周期钩子函数只能执行1次,也就无法监测到路由参数变化
如果想要响应路由参数变化,可通过watch监听$route(路由规则相关信息)
// 侦听器
watch: {
// 侦听route变化
$route(newR, oldR) {
// 打印路由参数
console.log(newR.params.id)
}
}
路由传参处理
问题:当一个组件需要再多个路由中使用的时候,就尽量不要使用$route进行数据获取
解决方案
通过路由props设置数据,并通过组件props接收,如果不设置props默认使用$route传参
<body>
<div id="app">
<!-- 写三个link,三个组件映射到同一个路由 -->
<router-link to="/user/1">用户1</router-link>
<router-link to="/user/2">用户2</router-link>
<router-link to="/user/3">用户3</router-link>
<!-- 视图: 视图中传入数据参数 -->
<router-view :data="users"></router-view>
</div>
<script>
// 使用的组件
const user = {
// id:路由规则中的动态设置名字,下面/user/:id中:后面的部分,同名!!!!!!!
// data顶部视图传入的数据,下面使用data,根据id显示不同内容!!!!!!!!!
props: ['id', 'data'],
template: `
<div>
<p>用户{{id}}的功能</p>
<p>{{data[id-1].content}}</p>
</div>`
}
// 路由实例
const router = new VueRouter({
// 路由映射规则
routes: [{
// :id部分是动态的,会根据上面router-link进行设置
path: '/user/:id',
component: user,
// 允许接收参数!!!!!!!!!!!!!
props: true
}]
})
// vue实例
const vm = new Vue({
// 挂载元素
el: '#app',
data: {
// 传递的数据!!!!!!!!!!!!!!!!
users: [{
id: 1,
content: '我是用户1内容'
}, {
id: 1,
content: '我是用户2内容'
}, {
id: 1,
content: '我是用户3内容'
}]
},
// 注入路由
router,
// 侦听器
watch: {
// 侦听route变化
$route(newR, oldR) {
// 打印路由参数
console.log(newR.params.id)
}
}
})
</script>
</body>
路由传参的其他方式
当包含多个视图的时候,需要将路由的props设置为对象
// 多个视图
const routes = [{
path: '/user/:id',
// 这个路由关联多个视图
components: { default: one, bar: tow},
// props写成数组可以分别设置是否是否需要传参
props: {
// 默认视图需要传参
default: true,
// 设置bar视图不需要传参
bar: false
}
}]
如果希望给props传递静态参数,可以把props里面的某个组件的选项设置为对象,呢哦不属性会被绑定到这个组件的props上
const bar = {
//视图直接使用组件静态数据a和b
props: [a ,b],
template: `.........................`
}
// 多个视图
const routes = [{
path: '/user/:id',
components: {
default: one,
bar: tow
},
props: {
default: true,
// 传递静态数据,静态数据直接写成对象结构
bar: {
a: 'one',
b: 'tow'
}
}
}]
嵌套路由
实际开发中,路由通常是由多层嵌套组合而成的,这时需要使用嵌套路由配置
利用路由映射关系的children属性设置
<!-- 路由嵌套 -->
<body>
<!-- 挂载节点 -->
<div id="app">
<!-- 父亲路由 -->
<router-link to="/user">用户</router-link>
<!-- 父亲视图 -->
<router-view></router-view>
</div>
<script>
// 将全部组件书写在一个对象内部
const data = {
// 父亲点击后视图
title: {
// 内部在嵌套一层路由和视图,这里的router-view不需要命名,和上面不在同一层级
template: `
<div>
<p>我是父亲路由的视图内容</p>
<router-link to="/user/one">用户1</router-link>
<router-link to="/user/tow">用户2</router-link>
<router-link to="/user/three">用户3</router-link>
<router-view></router-view>
</div>
`
},
// 全部(子路由)用户内容
users: [
{template: `<div>用户1内容</div>`},
{template: `<div>用户2内容</div>`},
{// 在用户3下面我又嵌套了一层路由
template: `
<div>
<p>用户3内容</p>
<router-link to="/user/three/info">信息</router-link>
<router-view></router-view>
</div>`}
],
// 用户3的信息
threeInfo: {
template: `<div>用户3信息</div>`
}
}
// 路由实例
const router = new VueRouter({
// 映射关系
routes: [{
// 父路由信息
path: '/user',
// 这里组件可以使用对象中的成员
component: data.title,
// 嵌套的子路由放在children内部,书写方式和上面一样,如果还有子路由可再向下写
children: [
{path: '/user/one',component: data.users[0]},
{path: '/user/tow',component: data.users[1]},
{path: '/user/three', component: data.users[2],
// 用户3下面因为还有路由所以还要写children
children: [{path: '/user/three/info',component: data.threeInfo }]}
]
}]
})
// vue实例
new Vue({
el: '#app',
router
})
</script>
</body>
一定要注意路由router-link的to属性,子路由要加上父路由的前缀
编程式导航
通过方法设置导航
- 使用router.push()用来导航到一个新的URL
- router-link的to属性使用绑定方式时,也可以属性对象结构
<body>
<!-- 编程式导航 -->
<!-- 两个按钮执行click事件触发 vm.$router.push 方法 -->
<button onclick="vm.$router.push('/user/1000')">1000</button>
<button onclick="vm.$router.push({path: '/user/100'})">100</button>
<div id="app">
<!-- 使用v-bind绑定to属性也可以实现vm.$router.push方法,和上面效果一样 -->
<router-link :to="{path: '/user/10000'}">10000</router-link>
<router-view></router-view>
</div>
<script>
// 组件
const user = {
// 使用动态路由$route.params.id数据
template: `<p>{{$route.params.id}}</p>`
},
// 映射关系
routes = [{
// 这里采用动态路由
path: '/user/:id',
component: user
}],
// 路由实例
router = new VueRouter({
routes
}),
// vue实例
vm = new Vue({
el: '#app',
router
})
// vm.$router.push({path: "/user/10000"})
</script>
</body>
命名路由
通过名称表示一个路由,如果路由名字过长起名字更好表达
- 设置路由给路由添加name
- 在push中通过name导航到这个路由,参数通过params设置
- 也可以在router-link中使用
<body>
<!-- 编程式导航,使用路由命名,这里直接使用重命名school!!!!!!!!!!!!! -->
<button onclick="vm.$router.push({name: 'school', params:{id : 1, a: '我是1'}})">1</button>
<button onclick="vm.$router.push({name: 'school', params:{id : 2, a: '我是2'}})">2</button>
<div id="app">
<router-link :to="{name: 'school', params:{id : 3, a: '我是3'}}">点击</router-link>
<router-view></router-view>
</div>
<script>
// 组件
const user = {
// 这路可以使用导入的数据,很方便!!!!!!!!!!!!1
template: `<p>{{$route.params.a}}</p>`
},
// 映射关系
routes = [{
// 这里采用动态路由
path: '/user/name/:id/school',
// 把这个路由命名为school,之后就可以直接使用名字访问
name: 'school',
component: user
}],
// 路由实例
router = new VueRouter({
routes
}),
// vue实例
vm = new Vue({
el: '#app',
router
})
</script>
</body>
可以在组件内部访问$route.params下的所有参数
重定向
可以实现访问一个地址的时候实际上访问的是另一个路由,使用映射关系设置选项的redirect属性
<body>
<!-- 重定向 -->
<div id="app">
<!-- 三个路由分别指向三个地址 -->
<router-link to="./" tag="button">转到/</router-link>
<router-link to="/user/5" tag="button">转到/user/5</router-link>
<router-link to="/user" tag="button">转到/user</router-link>
<router-view></router-view>
</div>
<script>
// 组件
const up = {
template: `<p>这是/的内容</p>`
},
user = {
template: `<p>这是/user/{{$route.params.id}}的内容</p>`
}
// 映射关系
routes = [{
path: '/',
component: up
},
{
path: '/user/:id',
component: user
},
{
path: '/user',
// 设置重定向到 / ,当访问到 /user 的时候程序会访问 /
redirect: '/'
}
],
// 路由实例
router = new VueRouter({
routes
}),
// vue实例
vm = new Vue({
el: '#app',
router
})
</script>
</body>
别名
美化路由的方式,可以将比较复杂的路由地址变更为简短的地址,起到访问长地址的效果,用户访问看到的地址更加美观
但是从原理上来说,最终访问的还是长地址,只不过展示出来的是短地址
<body>
<div id="app">
<!-- 使用正常方式访问 -->
<router-link :to="{name: 'my', params: {id: 10, data: 200}}">转到</router-link>
<router-link to="/one/20/tow/three/four/five/300">转到</router-link>
<!-- 使用别名访问,和上一个效果相同,但更简短 -->
<router-link to="/20/300">转到</router-link>
<router-view></router-view>
</div>
<script>
// 组件
const up = {
template: `<p>这是{{$route.params.id}}/{{$route.params.data}}的内容</p>`
},
// 映射关系
routes = [{
path: '/one/:id/tow/three/four/five/:data',
component: up,
name: 'my',
// 把id和data单独提出来建立一个别名,以后可以使用别名访问
alias: '/:id/:data'
}],
// 路由实例
router = new VueRouter({
routes
}),
// vue实例
vm = new Vue({
el: '#app',
router
})
</script>
</body>
导航守卫
可以在每次导航切换操作之前做对应操作,例如检测用户是否登陆,没有登陆跳到登陆页等等功能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入vue和vue router -->
<script src="./js/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<router-link to="/">首页</router-link>
<router-link to="/user">用户</router-link>
<router-link to="/name">名字</router-link>
<router-view></router-view>
</div>
<script>
// 组件
const sy = {
template: `<p>这是首页的内容</p>`
},
user = {
template: `<p>这是用户的内容</p>`
},
name = {
template: `<p>这是name的内容</p>`
},
// 映射关系
routes = [{
path: '/',
component: sy
},
{
path: '/user',
component: user
},
{
path: '/name',
component: name
},
],
// 路由实例
router = new VueRouter({
routes
}),
// vue实例
vm = new Vue({
el: '#app',
router
})
// 导航守卫
// 参数1:切换的目标路由
// 参数2:从哪个路由切换过来
// 参数3:相当于回调函数,内部可以书写路由地址,注意,导航守卫功能里面必须有next执行一次(有且只能执行一次)
router.beforeEach((to, from, next) => {
// 打印to和ftom看一下
console.log('from:' + from.path, 'to:' + to.path)
// 判断我们要访问的是不是user,如果是跳转到name,如果不是继续访问
if (to.path === '/user') {
next('/name')
} else {
next()
}
// 注意如果不写next是不允许的,必须书写
// 如果next内部书写false,则跳转会被阻止,同样,不写next也会被阻止
// next(false)
})
</script>
</body>
</html>
histor模式
通过vue Router实例的mode选项设置,这样url会更加美观(可以去掉#),但同样需要后端支持避免出现问题
// 直接在实例内部书写
router = new VueRouter({
mode:'history'
routes
}),