Hash地址与组件之间的对应关系
用户点击了页面上的路由连接
导致了URL地址栏中的Hash值发生了变化
前端路由监听到了Hash地址的变化
前端路由吧当前Hash地址对应的组件渲染到浏览器中
–>通过浏览器的onhashchange事件监听Hash值的变化
在生命周期函数created()于组件生成时就立即用onhashchange事件监听Hash值的变化
再通过location.hash获取浏览器URL地址栏中的Hash值,根据Hash值对应修改data中的值
然后通过component标签is属性读取data中的对应值动态渲染组件
vue-router是官方的路由解决方案,只能结合vue项目使用,实现管理SPA(单页面)项目中的组件切换
npm i vue-router@3.5.2 -S
// src源代码目录下,新建router/index.js路由模块
// 导入 Vue和 VueRouter的包
import Vue from 'vue'
import VueRouter from 'vue-router'
// 调用 Vue.use() 函数,把 VueRouter 安装为 Vue 插件
Vue.use(VueRouter)
// 创建路由的实例对象
const router = new VueRouter({
routes, // 路由数组
mode: 'history', // mode为路由模式 history历史路由 hash 哈希路由
base: 'app' , // 根路径 在项目所有路由路径前会默认加上根路径app
scrollBehavior(to, from, position){
// 滚动行为 记录浏览器的滚动记录 (只能记录根元素 无实际用处)
}
})
// 向外共享路由的实例对象
export default router
// main.js中
// 导入路由模块,为了拿到路由的实例对象
//在进行模块导入时,如果给定的是文件夹,则默认导入这个文件夹下的index.js,所以可以省略后面
import router from '@/router'
new Vue({
render: h => h(App),
//在Vue项目中,要想把路由用起来,必须把路由的实例对象,通过下面的方式进行挂载
// router: 路由的实例对象
router
}).$mount('#app')
占位符
<router-view></router-view>
只要在项目中安装和配置了vue-router,就可以使用router-view这个组件
是一个单纯的占位符,作用类似component,将对应的组件渲染到其中
路由链接
// 在src下的router/index.js路由模块
const router = new VueRouter({
//routes是一个数组,作用:定义 "hash地址" 与 "组件"之间的对应关系
routes: [
// 路由规则
// 路径省略#,会自动解析
{ path: '/1', component: one }, //component为需要展示在router-view标签中的组件
{ path: '/2', component: two },
{ path: '/3', component: three }
]
})
跳转标签
<a href="#/3">组件3</a> <!-- old -->
<router-link to="/3">组件3</router-link> <!-- new -->
<!-- 支持location对象写法 v-bind绑定 -->
<router-link :to="{ path: '/3' }">组件3</router-link>
<!-- path路径跳转时不能用params携带参数 因为params参数就在path的值上面 -->
<router-link :to="{ path: '/3', params: { id:456, c:789 }}">组件3</router-link> <!-- x 无效传参! -->
<!-- path路径跳转时只能用query携带参数 -->
<router-link :to="{ path: '/3', query: { id:456, c:789 }}">组件3</router-link>
<!-- name名称跳转时可以用params携带参数 但路由必须配置动态参数 否则刷新会丢失 -->
<router-link :to="{ name: 'three', params: { id:456, c:789 }}">组件3</router-link>
无论是name还是path的形式来指定params参数 在跳转之后的页面刷新之后都是只能得到访问url上面的对应的动态参数
当安装和配置了vue-router后,就可以使用router-link来替代普通的a链接(浏览器中会解析成a标签)
router-link
属性
to属性与herf属性效果相同,且不需要加#,会自动解析
replace属性 替换路由栈中的页面
当跳转组件与router-link
相匹配时,router-link
会添加route-link-active的类名
路由重定向是当用户访问地址A的时候,强制用户跳转到地址C,从而展示特定的组件页面
通过路由规则的redirect属性,指定一个新的路由地址,实现路由的重定向
// 在src下的router/index.js路由模块
import one from '@/components/1.vue'
import two from '@/components/2.vue'
import three from '@/components/3.vue'
const router = new VueRouter({
// 在routes 数组中,声明路由的匹配规则
routes: [
// 当用户访问 / 的时候,通过redirect 属性跳转到 /1 对应的路由规则
{ path: '/', redirect: '/1' },
{ path: '/1', component: one },
{ path: '/2', component: two },
{ path: '/3', component: three }
]
})
通过路由实现组件的嵌套展示,叫嵌套路由
点击父级路由链接显示模板内容,模板内容中又有子级路由链接
点击子级路由链接需要显示子级模板内容
<!-- 在组件3模板中 -->
<!-- 子级路由链接 -->
<router-link to="/3/tab1">tab1</router-link>
<router-link to="/3/tab2">tab2</router-link>
<!-- 子级路由占位符 -->
<router-view></router-view>
// 在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: '/', redirect: '/1' },
{ path: '/1', component: one },
{ path: '/2', component: two },
{ // three页面的路由规则(父级路由规则)
path: '/3',
component: three,
children: [ //通过children属性,嵌套声明子级路由规则
//子路由路径不需要加/
{ path: 'tab1', component: Tab1 }, //访问/3/tab1时展示Tab1组件
{ path: 'tab2', component: Tab2 } //访问/3/tab2时展示Tab2组件
]
}
]
})
在设置路由规则时可以设置name属性用于跳转
// 在src下的router/index.js路由模块
const router = new VueRouter({
//routes是一个数组,作用:定义 "hash地址" 与 "组件"之间的对应关系
routes: [
// 路由规则
// 路径省略#,会自动解析
{ path: '/1', component: one, name:'1' }, //component为需要展示在router-view标签中的组件
{ path: '/2', component: two, name: '2'},
{ path: '/3', component: three, name:'3'}
]
})
<router-link :to="{name: '3'}">组件3</router-link> <!-- 使用name属性跳转需要v-bind -->
在设置路由规则时可以设置alias别名同样可以进行跳转
设置路由别名时必须携带 ‘/’ ,当访问路由别名的路径时会加载对应的组件
// 在src下的router/index.js路由模块
const router = new VueRouter({
//routes是一个数组,作用:定义 "hash地址" 与 "组件"之间的对应关系
routes: [
// 路由规则
// 路径省略#,会自动解析
{ path: '/1', component: one, name:'1', alias:['/n','/c']},
// 可以设置为数组或单个值
{ path: '/2', component: two, name: '2', alias:'/abc'},
{ path: '/3', component: three, name:'3', alias:'/u/:id'}
// 可以动态路由配合使用
]
})
当需要页面默认显示其中一个子路由时
1.通过路由重定向
{ // three页面的路由规则(父级路由规则)
path: '/3',
component: three,
redirect: '/3/tab1'
children: [ //通过children属性,嵌套声明子级路由规则
{ path: 'tab1', component: Tab1 }, //访问/3/tab1时展示Tab1组件
{ path: 'tab2', component: Tab2 } //访问/3/tab2时展示Tab2组件
]
}
当显示3组件时重定向到/3/tab1显示tab1组件
2.空路径
通过设置空路径也能实现
如果children数组中,某个路由规则的path为空字符串,则这条路由规则叫做默认子路由
{ // three页面的路由规则(父级路由规则)
path: '/3',
component: three,
redirect: '/3/tab1'
children: [ //即显示3组件时默认显示Tab1
{ path: '', component: Tab1 },
{ path: 'tab2', component: Tab2 }
]
}
对应的组件中的子路由链接也需要修改
<!-- 在组件3模板中 -->
<!-- 子级路由链接 -->
<router-link to="/3">tab1</router-link>
<router-link to="/3/tab2">tab2</router-link>
<!-- 子级路由占位符 -->
<router-view></router-view>
简单来说就是路由元信息,也就是每个路由身上携带的信息。
meta的作用是通过meta对象中的一些属性来判断当前路由是否需要进一步处理
const router = new VueRouter({
routes: [
{
path: '/3',
component: three,
children: [
{
path: 'tab1',
component: Tab1,
meta: { isRecord: true }
}
]
}
]
})
根据上面的路由配置,/3/tab1 这个 URL 将会匹配父路由记录以及子路由记录。
一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。因此需要遍历 $route.matched 来检查路由记录中的 meta 字段。
全局守卫检索元字段
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!auth.loggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // 确保一定要调用 next()
}
})
通过this.$router.addRoutes()
可以实现动态添加路由数组
this.$router.addRoutes([
{
name: 'p1',
path: '/p1',
component: P1,
beforeEnter: (to, from, next) => {
// ...
},
meta: {
auth: true
}
},
{
name: 'p2',
path: '/p2',
component: P2,
beforeEnter: (to, from, next) => {
// ...
},
meta: {
auth: false
}
}
])
可以在原有的路由数组中通过方法新增路由数组,来实现多个路由的添加
this.$router.addRoutes()
在3的路由版本中已废弃,但仍能使用。在4的路由版本中已不能使用
在4的路由版本中使用this.$router.addRoute()
进行代替,只能添加一个路由规则,如果该路由规则有name属性,
并且已经存在一个与之相同的名字,则会进行覆盖
this.$router.addRoute({
name: 'p1',
path: '/p1',
component: P1,
beforeEnter: (to, from, next) => {
// ...
},
meta: {
auth: true
}
})
当有模块下有子路径时就需要动态路由参数匹配
<router-link to="/movie/1">电影1</router-link>
<router-link to="/movie/2">电影2</router-link>
<router-link to="/movie/3">电影3</router-link>
此时路由为/movie/1,与/movie不匹配就不能显示movie组件,要显示movie组件就需要配置路由规则
{ path: '/movie/1', component: Movie },
{ path: '/movie/2', component: Movie },
{ path: '/movie/3', component: Movie }
此时路由规则的复用性差
动态路由参数概念
把Hash地址中可变的部分定义为参数项,提高路由规则的复用性
在vue-router中使用( : )来定义路由参数项
如果没有定义参数项就使用this.$router跳转且携带params参数时 params参数刷新就会丢失
//路由中的动态参数以 : 进行声明,冒号后面时动态参数的名称
{ path: '/movie/:id', component: Movie }
当需要用到后面的参数id时,通过this.$route.params.参数名 来获取
参数在vue实例中的$route对象中的params对象中
<!-- this.$route 是路由的 参数对象 -->
<!-- this.$router 是路由的 导航对象 -->
<h3>{{ $route.params.id }}</h3>
<!-- 在组件模板中使用vue实例中的属性可以不加this -->
props获取数据
通过 this.$route.params.参数名 来获取参数比较麻烦
可以通过props属性直接把参数赋值到props中
//可以为路由规则开启 props 传参,从而方便的拿到动态参数的值
{ path: '/movie/:id', component: Movie,props: true }
组件中也要定义对应的props数据来接收
export default {
name: 'app',
props: ['id']
}
其他
注意1: 在hash地址中,/后面的参数项,叫做 路径参数
在路由 参数对象 中,需要使用 this.$route.params 来访问路径参数
注意2: 在hash地址中, ? 后面的参数项,叫做 查询参数
在路由 参数对象 中,需要使用 this.$route.query 来访问查询参数
注意3: 在 this.$route 中,path 只是路径部分; fullPath 是完整的地址
eg:
/movie/2?name=zs&age=20 是 fullPath 的值
/movie/2 是 path 的值
在浏览器中,点击链接实现导航的方式,叫做声明式导航
普通网页中点击a链接,vue项目中点击router-link都属于声明式导航
在浏览器中,调用API方法实现导航方法,叫做编程式导航
普通网页中调用location.href跳转到新页面的方式,属于编程式导航
// 跳转到指定的hash地址,并增加一条历史记录
// 参数都是location对象或者是字符串
this.$router.push('hash 地址')
this.$router.push({ name: '3', params: { userId: 123 }}) // 命名式路由的跳转
// 跳转到指定的hash地址,并替换掉当前的历史记录
this.$router.replace('hash 地址')
// 实现导航历史前进后退
this.$router.go(数值 n)
// go(-1)表示后退一层 如果后退的层数超过上限,则原地不动
// 在历史记录中,后退到上一个页面
this.$router.back()
// go(-1)表示后退一层 如果后退的层数超过上限,则原地不动
// 在历史记录中,前进到下一个页面
this.$router.forward()
// go(-1)表示后退一层 如果后退的层数超过上限,则原地不动
导航守卫可以控制路由的访问权限,强迫路由跳转
每次发生路由的导航跳转时,都会触发全局前置守卫
因此在全局前置守卫中,可以对每个路由进行访问权限的控制
// 于router/index.js创建路由实例对象
const router = new VueRouter({...})
// 调用路由实例对象的beforeEach方法声明全局前置守卫
// 每次发生路由导航跳转时,都会触发 fn 回调
router.beforeEach(fn)
function fn (to, from, next) {
// to 是将要访问的路由的信息对象
// from 是将要离开的路由的信息对象
// next 是一个函数,调用 next() 表示放行,允许这次路由导航
next()
//声明函数中先加 next() 如果不加next()则所有路由均被拦截
}
next( )函数的调用
next() 直接放行
next(‘/login’) 强制跳转到登录路由
next(false) 不允许跳转
当next跳转且需要携带参数时则需要传参一个对象
next({
//跳转路由为login且携带query参数
path: '/login',
query: { redirect: to.fullPath }
})
// 于router/index.js创建路由实例对象
const router = new VueRouter({...})
// 调用路由实例对象的afterEach方法声明全局后置钩子
// 每次发生路由导航跳转后,都会触发 fn 回调
router.afterEach(fn)
function fn (to, from) {
// to 是将要访问的路由的信息对象
// from 是将要离开的路由的信息对象
}
router.beforeResolve
注册一个全局解析守卫,和router.beforeEach
类似,区别是在导航被确认之前
同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就会被调用
export default {
data(){
return { }
},
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confim 前调用
// 不能获取组件实例'this'
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /3/:id 在/3/1和/3/2 之间跳转时
// 由于会渲染同样的 3 组件,因此组件实例会被复用,而这个钩子就会在这种情况下被调用
// 可以访问组件实例'this'
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对用路由时调用
// 可以访问组件实例'this'
}
}
beforeRouteUpdate
可以监听同组件的路由参数的变化,但在2.2新增
在旧版本中需要监听同组件的路由参数的变化,只能通过侦听器watch实现
// 如果当前路由跳转到当前路由仅仅是参数发生了变化,不会引起created和mounted的生命周期执行
// 也不会触发 beforeRouterEnter再次执行
watch: {
$route(nv){
console.log(nv)
}
}
const router = new VueRouter({
routes: [
{ path: '/1', component: one },
{
path: '/2',
component: two,
beforeEnter: (to, from, next) => {
// 声明在路由配置里的 独享守卫 在进入此路由之前会触发
}
}
]
})
权限路由
用全局前置守卫可以在用户访问需要权限的路由时进行处理
当需要权限访问的路由过多时,可以声明为一个数组,封装为一个json文件
然后用路由地址与数组进行比较,判断是否为需要处理
//路由封装为json文件中的数组pathArr
//to为全局前置守卫中的需要跳转到的路由 拿到路径path后进行检索
pathArr.indexOf(to.path) !== -1
//则为需要权限才能访问的路由
使用keepalive组件使路由内的组件缓存,防止刷新重新发送请求获取数据
监听元素的滚动条滚动事件,将滚动的距离存入sessionStorage中(防抖)
当重新进入页面时设置元素的滚动距离
使用lodash类库进行操作
<div class="sc" style="height: 80vh;overflow-y: auto" ref="sc" @scroll="fn1">
<!-- ... -->
</div>
// 使用 lodash 实现防抖 获取最后一次执行的结果 存入sessionStorage中
methods: {
fn1: _.debounce((ev) => {
console.log(ev.target.scrollTop)
window.sessionStorage.scheight = ev.target.scrollTop;
}, 200)
},
// 在activated生命周期函数 在组件激活时 获取到sessionStorage中存入的滚动距离 设置为元素的滚动距离
activated() {
const { scheight } = window.sessionStorage;
if (scheight) {
this.$refs.sc.scrollTop = window.sessionStorage.scheight;
}
}