14.vue-router 3.x
路由安装的时候不是必须的,可以等到使用的时候再装,如果之前没有安装的话,可以再单独安装。之前的终端命令行不要关闭,再重新开一个,还需要再package.json文件的依赖中添加。
如果忘记之前是否有安装过,可以在package.json文件中查看依赖是否有router。
vue是单页面应用(SPA,在一个页面中切换)。
路由:简单来说就是路径跟视图的对应关系(一个路径对应一个视图)。vue中的切换都是基于路由去切换的。
安装
npm install vue-router
//安装指定版本
npm install vue-router@3.2.0
npm i jquery --save(缩写-S)生产依赖 --save-dev(缩写-D)开发依赖 将jquery安装并保存到生产依赖或开发依赖中
而现在不用这么麻烦了,直接npm install jquery 直接自动保存到生产依赖中,如果需要保存到开发依赖中还是需要像之前一样
卸载:npm uninstall jquery
使用步骤
在使用组件名之前都需要引入组件所在的文件。
在 router 文件夹下的 index.js 文件中:
1.引入vue、 vue-router import Vue from 'vue'
import VueRouter from 'vue-router'
2.注册组件VueRouter,Vue.use(VueRouter)
(因为这是为vue量身打造的,只要不是vue的核心并且是为vue量身定做的才可以注册。如vue-router和vuex)
3.定义路由配置项(哪个路径对应哪个视图)。------重点。①路由的核心就是路径与视图的对应关系:使用路由懒加载的方式引入。②路由代表的不是整个页面,而是需要变动的某一块页面,需要在最大的组件App.vue中给路由视图留出位置。<router-view></router-view>
。
//在router文件下的index.js文件中使用组件名之前是要引入组件
import MyHome from '../views/MyHome'
import MyAbout from '../views/MyAbout'
//定义路由配置项:路径和组件视图
const routes=[
{
path:'/',
component:'MyHome'
},
{
path:'/about',
component:'MyAbout'
}
]
<!-- 在App.vue文件中合适的位置留出路由视图的位置 -->
<router-view></router-view>
component: 组件名 在文件顶部引入组件文件
4.创建 VueRouter 实例并导出,然后 在main.js的根组件中导入,传入配置项,挂载到实例上。
实战中使用路由的步骤(只是需要自己修改的部分,每个路由文件都需要的步骤不包括,如果是面试题还是上面的步骤):
1、在路由文件夹下的index.js文件下引入需要的组件文件;
或者一开始不引入,使用懒加载(建议采用该方法)
2、定义路由配置项:路径和视图的对应关系。
3、在App.vue文件中留出合适的路由视图渲染的位置。
<router-view></router-view>
const routes=[
{
path:'/',
component:组件名1
//使用懒加载的方法
//component:()=>import("路径")
},
{
path:'/about',
component:组件名2
},....
]
//使用案例:router文件夹下的index.js文件
import Vue from 'vue' //引入vue
import VueRouter from 'vue-router' //引入vue-router
//import MyHome from '../views/MyHome' //1 引入组件或使用路由懒加载
//import MyAbout from '../views/MyAbout'
Vue.use(VueRouter)//注册组件,给vue定制的,只要不是vue本身的东西使用的时候都得注册
//2 定义路由配置项:路径和视图的对应关系
const routes = [
{
path: '/', //默认路径开始显示的页面
// component: MyHome //组件名,前提是引入组件
//使用路由懒加载
component:()=>import('../views/MyHome.vue')
},
{
path: '/about',//路径不可重复,以 / 开头
//component:MyAbout
//使用路由懒加载
component:()=>import('../views/MyAbout')
}
]
//创建一个路由实例,导出去,在根组件main.js中引入,根配置项中才能使用路由
const router = new VueRouter({
routes
})
export default router
<!-- app.vue文件 -->
<template>
<div>
<h1>{{msg}}</h1>
<my-info></my-info>
<router-view></router-view> <!-- 3 路由视图就渲染在此-->
</div>
</template>
<script>
import MyInfo from './views/MyInfo.vue' //引入的组件
export default {
data(){
return {
msg:"我是App根组件"
}
},
components:{MyInfo}
}
</script>
<style scoped>
h1{
color: blue;
}
</style>
路由的两个核心
(1)定义路由配置项
(2)定义路由视图的显示位置(router-view)
路径定义规则:路径以斜线打头
页面与路由一定是一一对应的,一个页面一定要对应路由,否则找不到
**路由的懒加载:**之前是先把组件导入,不管用不用都把组件加载了,那些可能用不上的组件就会发生性能浪费。使用路由的懒加载的话就先不加载组件,等访问哪个页面再去加载哪个页面对应的路由。
component: () => import('../views/About.vue')
//只有访问该页面时才会调用该函数,加载
关于路径的写法
import Abc from "./demo"
1.如果当前目录下有demo文件夹,那么是去引入的demo文件夹下的index.js
2.如果不存在demo文件夹,会优先去找当前目录下的demo.vue文件
3.如果不存在.vue文件,那么会优先查找当前目录下的demo.js文件
路径中@
代表的是src目录,本质上是因为webpack内部配置的
嵌套路由
在整个项目系统中一进来就已经分了两个一级路由了:登陆注册页面和主页面,如果是没有登录注册就显示登录注册页面,如果登陆过了就显示主页面,这两个页面中显示一个。
二级路由是点击两边的导航而切换的页面。
注意点:每一级路由都必须要有该级路由的router-view。
1.哪个组件还有子路由,那么需要在该组件内部定义router-view,代表该组件的子路由显示在该位置上
2.子路由的配置,定义在父路由的children属性下,配置内容同父路由
子路由如果不加斜线,那么默认在上级路由上叠加,如果加了斜线,那么前面必须带上 上级路由。
//配置路由项
const routes=[
{
path:'/home',
name:"Home",//name属性叫什么都可以,但是不能重名,在路由跳转的时候会用到
component:()=>import("@/views/MyHome"),
children:[{
path:'/home/about',
name:"About",
component:()=>import("@/views/MyAbout")
}]
},
{
path:'/login',
name:"Login",
component:()=>import("@views/MyLogin")
}
]
//2 在父组件MyHome中找到路由试图router-view的位置。
路由的跳转
需要路由配置项的name属性
1.js形式的跳转:当用户请求成功/事件时的自动跳转
如果看到了某个方法是以$打头的,基本可以断定,该方法是Vue根实例的方法。
this.$router.push('字符串路径 /home/about ') //this指向的是当前组件
this.$router.push({ path: '/home/about' })
this.$router.push({ name: 'About'})
2.标签形式的跳转:当用户点击时的跳转情况,类似html中的a标签
<router-link to="/home/about">跳转 </router-link>
// 1.to 可以是字符串路径 还可以是对象(但需要是js的执行环境,加':') 2.:to="{path:'路径'}" 3.{name:"名字"}
//路由嵌套少的话使用路径较好,但是如果是嵌套情况较多的话建议是使用name跳转
3.浏览器的跳转:前提是浏览器要有记录
// 在浏览器记录中前进一步,等同于 history.forward()
this.$router.go(1)
// 后退一步记录,等同于 history.back()
this.$router.go(-1)
// 前进 3 步记录
this.$router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
this.$router.go(-100)
this.$router.go(100)
动态路由
多个路由匹配同一个页面,动态不写死。例如教务系统中查询成绩时的 www.qinghua. search / 20220101和www. qinghua. search/20220102,前半部分一样,都是查询页面。
使用场景:多个不同的路径匹配同样的页面。(也能够实现跨组件传递数据,拿到动态路径的名字id,但是这样的话就会出现必须传值的情况。)
动态路由匹配 在路径后面加/:名字
。这样在输入路径的时候名字可以是任意的,他们都对应的是同一个页面。
如果想要拿到地址的名字内容,在另一个页面中使用的话,使用$route.params.名字
(至于前面加不加this,如果是在html中写的则不加,如果是在js的methods中写的则要加)即可。
【this.$router拿到的是根路由实例,this.$route拿到的是当前路由实例。
】
如:
{ path: '/users/:id', component: User },
//可以同时存在多个动态路由
{ path: '/users/:id/:no', component: User },
注意只改后面动态路径参数是不会重新触发组件生命周期的。
但是如果页面中用到了动态路由的数据,那么切换动态路径的时候,是会触发组件更新的生命周期beforeUpdate和updated的(视图需要重新渲染)。
当有动态路径发生改变,想要在动态路径改变时做事情,组件生命周期是不会发生变化的,(只有页面中用到了动态路由的数据时才会发生变化,这样就很不稳定),但是使用导航守卫中的组件内的守卫钩子beforeRouterUpdaete是能够检测到的,然后在里面写我们需要做的事情就行了。
动态路由的跳转:
1.this.$router.push({ name: 'user', params: { userId: '123' }}) //使用这种方法很方便,动态路径可以将params的值设置为变量
2.this.$router.push({ path: '/user/123' })//如果用这个的话还不如用下面的那个
2.this.$router.push( '/user/123')
//下面的写法是错误的,path和parmas不能一起写,一起写会导致不知道哪一个是动态路径了
router.push({ path: '/user', params: { userId: '123' }})//如果提供了 path,params 会被忽略
同样的规则适用于router-link标签
查询参数
不需要使用动态路径的。
使用场景:用于跨组件传递数据。(实现页面跳转并且把数据带过去在另一个页面中使用)
查询参数的跳转的时候直接在地址后面拼接?key=value
,不用像动态路由那样修改路由配置项。
router.push({ path: '/register', query: { plan: 'private' }})
router.push({ path: '/register?key=value'}})
router.push("/register?key=value&key2=value2")
接收数据的页面使用该数据时:this.$route.query.key
。
注意 name和params配套
path和query 配套(因为路径后面没有冲突,直接就是数据)
动态路由和查询参数的相同点:都能实现跨页面传递数据;
不同点:
1.跳转方式:动态路由 name params;查询参数 path query.
2.动态路由更多的用于多个路径匹配同一个页面中;查询参数更多用于跨组件传递数据。
而且使用查询参数不需要在路由中定义东西的,不需要改路由配置项,而动态路由需要先改路由表
/:id
。3.动态路由传递参数不方便,不管用不用都必须传值。
注意:query查询参数传递的数据不要是引用数据类型的数据,只能传基本类型的数据,如果非要想传对象,可以将其转为字符串类型的数据。
捕获所有路由
访问的路由不存在,匹配不上时,显示的是404页面时使用。
{
// 会匹配所有路径
path: '*',
component:()=>import("@/views/NotFound")
}
{
// 会匹配以 `/user-` 开头的任意路径
path: '/user-*'
}
当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。
新版本不限制 通配符的存放位置,也就是说就算是放在最开始,其他路由也是能匹配上的,不过不推荐
组件路由对象
在组件中通过this.$route拿到当前组件的路由信息
{name:"组件的名字",fullPath:"完整路径",path:"路径",matched:[匹配上的所有路由,一级路由匹配上的是一个,嵌套路由匹配上的是多个],meta:"路由元信息",params:"动态路径参数",query:"查询参数"}
重定向和别名
重定向:“重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b。
在路由配置项中添加redirect:"要跳转的路径".
const routes =[
{
path:'/home',
name:"Home",
redirect:'/login', //重定向:加了这个一进入home页面就会自动跳转到login页面,URL发生了改变
alias:'/abc', //别名:URL没有发生改变,通过这两个名字都能访问到 使用的时候很少
component:()=>import ('@/views/MyHome'),
children:[
{
path:'about',
name:'About',
component:()=>import('@/views/MyAbout')
}
]
},
]
重定向使用场景:如淘宝在未登录时购买物品一直会跳转 到登录页面。// 或者 对于管理系统而言,一级路由是登录页面和主界面,在进入主界面之后只显示头部和侧导航栏,中间的内容是不显示的,这时如果使用重定向,在进入主界面之后就自动显示二级路由的一部分内容。 // 或者 输入网址后但进入的网站的网址是另一个的情况,网址有风险的情况。
重定向的目标也可以是一个命名的路由:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
别名:/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样. 使用的时候很少
在路由配置项中添加:alias:"/abc"
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
“别名”的功能让你可以*地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。
别名的使用场景:不用按照子父级关系定义路径了,之前子路由的路径得在父路由路径的基础上添加,使用别名后,可以直接写子路由的路径,不用再受父级路径的影响了,但是新版本修改后 不用别名就已经支持了。
路由组件传参(针对动态路由而言的)
可以将params直接映射到组件的属性中。
步骤:将路由组件中的路由配置项的属性props设置为true,这样动态路径就会作为参数传入到组件中,然后在组件中接收一下就可以直接用 。
使用动态路由this.$route.params.id
之前的做法,能拿但是很麻烦:
const User = {
template: '<div>User {{ $route.params.id }}</div>'
//2 在组件中要拿到id
}
const router = new VueRouter({
routes: [{ path: '/user/:id', component: User }]
//1 动态路由/:id
})
用了组件传参后:
const User = {
props: ['id',"num"],
//2 在相应的组件中的js中(不是放在data中,与data同级)接收一下传过来的props后可直接使用
template: '<div>User {{ id }} {{ num }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id/:num', component: User, props: true },
//1 将路由组件中路由配置项的属性props设置为true,这样动态路径就会作为属性参数传入到组件中,然后在组件中接收一下就可以直接用
]
})
props的来源:现在props可能是父-子传值中父组件传过来的,也有可能是路由中带的。
路由模式
默认的路由模式是hash模式,在创建项目的时候有问是否选择history模式,我们当时选择的是N。
hash模式的特点:URL地址栏是拼接在#后面的,#是一定会在的,即使是删掉也会在。hash模式丑,实际中都不使用。
改路由模式的方法:在路由文件创建路由对象时添加一个mode属性
const router=new VueRouter({
routes,
mode:"history" //将路由模式改为history模式
})
hash模式和history模式:
(面试题)history模式使用时存在的最大问题:是会导致刷新404的问题。
原因:hash模式是基于hash值来做的,#后面的hash值是不会发往后台的,那是前端自己定义的路由自己用的,发送请求查找服务器时是会忽略掉#后面的内容的,是按照#前面的内容查找的,是能成功查找到的;而history模式是按照完整的路径地址查找的,刷新时会按照完整的路径去后台服务器中查找,但是由于这个地址时前端定义的,在服务器是没有这个路径的,是无法在后台找到的,就会发生找不到页面404。(如hash模式的http://localhost:8080/#/home在向服务器发送请求时发送的地址只是#前面的http://localhost:8080/,是能够成功找到的;而history模式发送请求时使用的是完整的地址http://localhost:8080/home,该地址在服务器中是找不到的,出现404。要解决这个问题就需要后端配置nginx反向代理做重定向,当请求的路径不存在时重定向到首页。)
当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!
不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。
要解决这种问题就需要后端做一个nginx
做一个反向代理,做重定向,如果请求的路径不存在,重定向到首页。----这个问题是由后端配置的,
所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
如果面试官继续往下问是怎么做重定向的?那就回答是由后端做nginx代理做的,不是前端做的。
导航守卫/路由守卫
限制哪些能跳转,哪些不能跳转。
使用场景:如淘宝中未登录就支付是不可以的,有路由守卫拦下来。/ 或者权限管控。
全局前置守卫
全局:访问项目中的任何一个路由都会拦下来;前置:在还没跳转之前就拦下来。
使用场景:一般用来做整个项目的管控,如未登录时要求要先去登录。
beforeEach不用主动去调用,当有组件之间的跳转时就会自动的触发。
//在router文件夹下的index.js文件中创建的路由实例
const router = new VueRouter({ ... })
//beforeEach方法是我们创建的路由实例router的方法
router.beforeEach((to, from, next) => {
//根据判断条件能否进入到下一个页面
})
-
to: Route
: 即将要进入的目标 路由对象 (路由对象就是this.$route拿到当前组件的路由信息) -
from: Route
: 当前导航正要离开的路由 -
next: Function
: 一定要调用该方法来 resolve 这个钩子。执行效果依赖next
方法的调用参数。—next方法需要主动调用。下面第一种和第三种是用的最多的。-
next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。主动调用next()表示放行,直接往下进入下一个组件。----------------使用较多的 -
next(false)
: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from
路由对应的地址。从哪来回哪去。 -
next('/')
** 或者 **next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next
传递任意位置对象,且允许设置诸如replace: true
、name: 'home'
之类的选项以及任何用在router-link
的to
prop 或router.push
中的选项。强制去另一个路径。(如淘宝种未登录去支付被强制转换到登录页面)------------------使用较多的 -
next(error)
: (2.4.0+) 如果传入next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给router.onError()
注册过的回调。
-
注意避免死循环,下面的写法是死循环
let flag = false;//存储的是登录的状态
//全局前置守卫
router.beforeEach((to, from, next) => {
if(flag){
next();//直接放行
}else{
next("/login") 跳转到登录页面。在未登录状态时会发生死循环
}
})
先看是否登录,未登录要去登录页面,而想要去登录页面又要检查是否登录,一看未登录就要去登录页面,而主只要想去其他页面就要进行检查,这样就陷入了死循环。
解决方法:在状态是未登录,需要转向登陆页面时做一个判断,判断当前是否是要去登录页面,如果是的话放行,否则还是得要去登陆页面。加个验证就ok了。
let flag = false;//存储的是登录的状态
//全局前置守卫
router.beforeEach((to, from, next) => {
if (flag) {
next();//直接放行
} else {
//在是未登录状态需要转向登录页面时加一个验证:如果是要去登录页面的,就让他过去,否则如果是要去其他页面的话不让过,还是需要去登陆页面
if (to.path == '/login') {
next()
} else {
next('/login');//跳转到登录页面
}
}
})
全局解析守卫(用的很少)
在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
全局前置守卫和全局解析守卫只是守卫的时机不同。全局前置守卫是在进大门的时候检查,而全局解析守卫是在进入大门之后再进入每一个小门的时候检查。
全局后置钩子
钩子:在特定的情况下会自动运行的函数。
跳转到某个页面之后需要做事情,一般可以用组件的生命周期来写。全局后置钩子与组件自己的生命周期类似,一般就不用了。
//和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:
router.afterEach((to, from) => {
// ...
})
路由独享的守卫
不需要给全局加守卫,只给其中某些重要的页面加守卫。beforeEnter:(to,from,next)=>{}
//在路由配置项中写的
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内的守卫
在对应的组件.vue中写。
export default {
mounted(){
console.log(this.$route)
},
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
//使用场景:当有动态路径发生改变,想要在动态路径改变时做事情,组件生命周期是不会发生不会的,但是使用组件内的守卫是会发生变化的。
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
//进入该路由时执行,该路由中参数改变时执行,离开该路由时执行。
完整的导航解析流程
全局守卫先于局部守卫的执行。
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
路由元信息
定义路由的时候可以配置 meta 字段。通过this.$route
拿到meta信息。
const routes = [
{
path: '/home',
name: "Home",
meta:{a:"123"},//meta路由元信息
component: () => import('@/views/MyHome'),
children: [
{
path: 'about',
name: 'About',
alias: '/abc',
meta:{auth:"boss"},//meta路由元信息,权限,判断当前组件的meta是否是boss
component: () => import('@/views/MyAbout'),
}
]
},
]
使用场景:meta可以设置一些跟当前路由相关的自定义信息,比如页面标题,面包屑导航,页面权限等。
滚动行为
由于页面内容较多出现了滚动条,上面的内容不是我们想要看的,下面才是,我们想要跳转页面过来后直接定位到想要看的部分,不想手动向下滑动。
有滚动条的前提下才生效,没有滚动条不生效
在路由实例中加:
const router = new VueRouter({
routes,
//更改路由模式
mode: "history",
//滚动行为
scrollBehavior(to, from) {
//to from是路由对象 我们不想所有的页面都滚动到相同的位置,to用于判断去到的是a页面滚到这里,如果去到的是b页面滚动到那里
console.log(to, from)
// return 期望滚动到哪个的位置 x是离左边距离 y是离顶部距离
//有滚动条的前提下才生效,没有滚动条不生效
return { x: 0, y: 1000 }
}
})