目录
备注:遗留问题,props中params无法二次渲染(已解决)
路由vue-router
1.简单介绍
Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。功能包括:
- 嵌套路由映射
- 动态路由选择
- 模块化、基于组件的路由配置
- 路由参数、查询、通配符
- 展示由 Vue.js 的过渡系统提供的过渡效果
- 细致的导航控制
- 自动激活 CSS 类的链接
- HTML5 history 模式或 hash 模式
- 可定制的滚动行为
- URL 的正确编码
以上是官方介绍,理解即是一种插件,可以是开发变得更简单,更清晰。应用在单页面开发,是一种极好的技术
2.使用路由
首先直接在项目文件夹根目录启动命令行
使用npm i vue-router安装
这里注意要根据vue的版本安装vue-router,
用过配置文件package.json文件查看vue版本,
vue2版本对应vue-router3,安装时直接
npm install vue-router@3
同理,如果是vue3版本,安装则
npm install vue-router@4
(当然,通过webpack工具生成项目则在生成时候已经配置好路由相关文件)
使用工具生成项目的文件会直接在src下生成路由文件
把需要路由的组件成为路由组件,把没有路由的组件成为一般组件, 将需要应用路由的路由组件统一放入到src文件夹的pages文件夹中,
下面是路由文件夹router中index.js文件,即路由器
使用路由时需要先引入vue,然后引入vue-router插件
在路由器的文件中,我们需要将所有路由的组件都引入进来,进行路由定义
通过插件创建路由器并暴露
//以下是创建路由器
import Vue from 'vue'
//引入路由插件
import Router from 'vue-router'
//引入路由组件
import About from '../pages/About.vue'
import Home from '../pages/Home.vue'
// 使用路由
Vue.use(Router)
//创建路由器
export default new Router({
routes: [
{
path: '/about',
name: 'About',
component: About
},
{
path: '/home',
name: 'Home',
component: Home
},
]
})
对路由器进行基本配置后,在入口文件main.js文件中进行引入并注册,找到router文件夹会默认直接读取index.js文件,然后将router配置在vue中,
import router from './router'
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
完成上述操作后,在一个一般组件中放置
<router-link to="/about">Go to About</router-link>
其中的"/about"和路由器中的path属性相匹配,
然后再下方放置路由出口
<router-link to="/about">Go to About</router-link>
<router-view></router-view>
运行项目
至此路由器基本使用完成
当点击about的时候,会显示about的内容,此时的home路由组件其实是被销毁的
并且所有的vueComponent上都会有$router和$route属性,$router是不变的,route是不同的每个组件上有不同的route,route中存放当前的挂载地路由组件的信息,而router则是整个路由器和通用的路由方法
3.嵌套路由(多级路由)
多级路由是指当点击一个触发一级路由的元素后,展示一级路由内容时,在一级路由组件内,还有两个路由组件,在这个一级路由组件中,再次设定了点击元素触发二级路由的标签,这就打成了多级路由的效果
示例:
首先在路由组件文件夹内创建两个二级路由,
在路由器index.js文件中配置路由,制定两个二级路由在一级路由组件about中触发
首先引入两个二级路由组件,然后再一级路由about的配置项中,使用children属性,对两个二级路由进行配置,其中的path属性值不用再添加‘/’符号
import Vue from 'vue'
//引入路由插件
import Router from 'vue-router'
//引入路由组件
import About from '../pages/About.vue'
import Home from '../pages/Home.vue'
import Nav from '../pages/Nav.vue'
import Tes from '../pages/Tes.vue'
// 使用路由
Vue.use(Router)
//创建路由器
export default new Router({
routes: [
//一级路由about
{
path: '/about',
name: 'About',
component: About,
//二级路由
children:[
{
path:'nav',
component:Nav
},
{
path:'tes',
component:Tes
}
]
},
//一级路由home
{
path: '/home',
name: 'Home',
component: Home
},
]
})
在一级路由组件about.vue文件中
放置触发元素的routerlink,定义路由的出口
<template>
<div>
<h2>我是About的内容</h2>
<el-tag>
<router-link to="/about/nav">about二级组件nav</router-link>
</el-tag>
<el-tag type="success">
<router-link to="/about/tes">about二级组件tes</router-link>
</el-tag>
<!-- 二级路由出口 -->
<div class="nav-name"><router-view></router-view></div>
</div>
</template>
<script>
export default {
name:'About'
}
</script>
效果如下
4.命名路由
在路由器的配置项中配置路由时,使用name属性;在使用routerlink时就,在to前加:冒号,后面的值引号内写成对象,对象中name属性和路由中的name属性相同,这样就可以避免二三级路由路径长度的问题
5.路由传参
(1)query传参
创建三级子路由组件threeNav,并且配置在路由组件nav的子路由组件中,配置路由器index.js
import Vue from 'vue'
//引入路由插件
import Router from 'vue-router'
//引入路由组件
import About from '../pages/About.vue'
import Home from '../pages/Home.vue'
import Nav from '../pages/Nav.vue'
import Tes from '../pages/Tes.vue'
import ThreeNav from '../pages/ThreeNav.vue'
// 使用路由
Vue.use(Router)
//创建路由器
export default new Router({
routes: [
//一级路由about
{
path: '/about',
name: 'About',
component: About,
//二级路由
children:[
{
path:'nav',
component:Nav,
// 三级路由
children:[{
path:'threeNav',
component:ThreeNav
}]
},
{
path:'tes',
component:Tes
}
]
},
//一级路由home
{
path: '/home',
name: 'Home',
component: Home
},
]
})
在父路由组件Nav中有数据msg,需要将其传递给子路由组件threeNav,用于展示
<template>
<div>
<h2>我是about的二级组件nav的内容</h2>
<ul >
<li v-for="item in msg" :key="item.id">
<!-- <router-link
:to="`/about/nav/threeNav?id=${item.id}&test=${item.test}`">
{{item.id}}
</router-link> -->
<router-link :to="{
path:'/about/nav/threeNav',
query:{
id:item.id,
test:item.test
}
}">
{{item.id}}
</router-link>
</li>
</ul>
<!-- 三级路由threeNav出口 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'Nav',
data() {
return {
msg:[
{id:'001',test:'张三'},
{id:'002',test:'王五'}
]
}
},
}
</script>
子路由组件threeNav接收通过this.$route.query.test
<template>
<div>
{{this.$route.query.test}}
</div>
</template>
<script>
export default {
name:'Nav'
}
</script>
此时无论路由组件nav中有多少数据,都只需要一个路由组件threeNav就可以完成展示,而不需要定义多个路由组件进行频繁销毁切换,
(2)params传参
params传参和去query类似,但是在传递参数前需要在路由器中的路由配置中path选项需要占位符
并且在传递起始位置的路由组件中,params不能和path一起使用,只能和name
接收后通过params调用
<div>
{{this.$route.params.test}}
</div>
6.props配置项
路由的props配置项可以接收到路由组件传递过来的route,读取route中的数据例如params、query等
首先在路由器中配置props,谁接收数据,就在谁的路由中配置
//路由器中的部分代码
children:[{
path:'threeNav',
name:'threeNav',
component:ThreeNav,
props($route){
return {id:$route.params.id,test:$route.params.test }
}
}]
props配置项可以写成一个函数,接收一个固定参数即$route,把数据return一个对象的形式返回,
也可通过解构赋值简化代码
props({params}){
return {id:params.id,test:params.test }
}
传递方的路由组件,通过params传递
接收的路由组件,接收时就可以通过props接收,直接读取id和test,只不过本质是通过props
此时出现了问题,当点击001出现张三后,再次点击002就无法现实王五,(待解决)已解决,下文
但是如果将路由器中路由的params换成query,并将传参组件的params换成query,就可以成功切换
前面问题解决,如果要接收params参数,可以直接配置路由,
在接收时,可以直接用
7.编程式路由导航
通过push方法,压栈的方式切换路由组件,并且会保持历史记录
通过replace方法则会替换记录,不会保持历史记录
router上的go方法可以指定退步和前进的页面,并且可以自定义具体行进或后退多少步
编程式路由导航最大的优点就是受a标签的束缚,可以应用在按钮button等元素中
示例
<button @click.once="pushShow(item)"> {{item.id}}</button>
pushShow(i){
this.$router.push({
name:'three',
query:{
id:i.id,
test:i.test
}
})
}
通过路由器中的props传递,再由目标组件接收,效果即可完成
<template>
<div>
{{test}}
</div>
</template>
<script>
export default {
name:'ThreeNav' ,
props:['id','test']
}
</script>
官方go方法的示例说明
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)
8.缓存路由
当切换路由组件时,被换掉的路由组件将不会被销毁,而是缓存下来,二次切换到该组件时,里面的数据依旧存在,即使制定的路由组件不被销毁,缓存下来
使用方式就是在路由的视图出口标签router-view的外部,包裹标签keep-alive,并且在标签keep-alive中定义属性include的值为需要缓存路由组件的组件名
这里的组件名是指组件内部配置的name属性值,即如下图
如果想缓存多个路由组件,可以如下所示
上方的缓存路由依旧存在一个问题
当被缓存的路由,存在异步任务,比如定时器等,
例如在nav组件中有一个定时器,但是该组件有一个输入框,需要将这个组件定义为缓存路由,
当将nav定义缓存路由以后,这个定时器无法销毁,依旧会继续执行,此时就需要借助新的两个生命周期钩子
在nav组件中定义两个生命周期钩子,这两个钩子需要定义才缓存路由组件中才会生效
activated()和deactivated()
需要使用钩子的组件
export default {
name:'ThreeNav' ,
props:['id','test'],
// 当该组件被激活时会执行
activated(){
console.log('nav被激活了');
},
// 当该组件失活时会执行
deactivated(){
console.log('nav失活了');
}
}
必须要使用了缓存路由的组件
这里在补充一个生命周期钩子
nextTick ,这个生命周期钩子将会在把真实dom放入页面渲染完成以后再执行,是个异步执行
this.$nextTick()
9.路由重定向
重定向也是通过路由器的routes配置来完成,下面例子是从 /home
重定向到 /
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
重定向的目标也可以是一个命名的路由:
重定向的目标也可以是一个命名的路由:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
相对用的较少,更多详情参考官网
10.路由元信息
有时,你可能希望将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的meta
属性来实现,并且它可以在路由地址和导航守卫上都被访问到。
简单理解就是需要将一个自定义的属性放到一个地方,vue提供了这个地方,就是meta配置项
定义路由的时候你可以这样配置 meta
字段:
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 只有经过身份验证的用户才能创建帖子
meta: { requiresAuth: true }
},
{
path: ':id',
component: PostsDetail
// 任何人都可以阅读文章
meta: { requiresAuth: false }
}
]
}
]
可以结合下一点的全局路由前置守卫达到和全局独享守卫类似的效果,用过前置守卫中判断to.meta.requiresAuth访问到值,进行权限的限制
11.路由守卫(重点)
守卫的分类很多,具体详情参考官网
(1)全局前置路由守卫
使用 router.beforeEach
注册一个全局前置守卫
切换前和初始化前会被调用
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
全局前置守卫可以定义部分逻辑代码,限制组件跳转,接收参数next
如果允许则成功跳转点击后的组件,如果不允许则跳转到制定的路由组件
// GOOD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
注:在.3.x版本以前接收第三个可选参数next(),在4.x版本中也并未废弃
(2)全局解析守卫
可以用 router.beforeResolve
注册一个全局守卫。这和 router.beforeEach
类似,因为它在 每次导航时都会触发,但是确保在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用。
当已经完全进入这个组件,就会被调用,这里完全是指已经经过全局前置守卫,以及组件内守卫、全局独享守卫后再调用
这里有一个例子,确保用户可以访问自定义 meta 属性 requiresCamera
的路由:
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... 处理错误,然后取消导航
return false
} else {
// 意料之外的错误,取消导航并把错误传给全局处理器
throw error
}
}
}
})
router.beforeResolve
是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。
(3)全局独享守卫
是指单独为一个路由组件,在路由器中配置,跟全局前置守卫类似,但是这里是只针对一个路由组件,只有前置没有后置
beforeEnter
守卫 只在进入路由时触发,不会在 params
、query
或 hash
改变时触发。例如,从 /users/2
进入到 /users/3
或者从 /users/2#info
进入到 /users/2#projects
。它们只有在 从一个不同的 路由导航时,才会被触发。(摘自官网)
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
注:通过全局前置守卫也可完成独享守卫的效果,利用元信息meta字段,对条件进行限制
(4)全局后置守卫
接收两个参数(也可接收第三个参数Navigation Failure ),可以用于分析、更改页面标题、声明页面等辅助功能以及许多其他事情
注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})
可以对错误,导航故障等进行处理
router.afterEach((to, from, failure) => {
if (!failure) sendToAnalytics(to.fullPath)
})
(5)组件内部守卫(三个)
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
以上转自官网
这三个守卫定义在路由组件中,和计算属性、data等平级。
beforeRouteEnter
守卫 不能 访问 this
,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建,但是可以通过next传递实例vm进行访问,注意只有beforeRouteEnter
可以next传递回调,另外两个不行,因为已经在组件中,可以访问到实例
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
离开守卫可以提示一些用户操作,beforeRouteLeave
这个 离开守卫 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false
来取消
beforeRouteLeave (to, from) {
const answer = window.confirm('确定返回吗?您还有修改未保存!')
if (!answer) return false
}
总结完整守卫导航流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫(2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫(2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入