基本路由
-
安装
vue-router
,命令npm i vue-router
(vue-router的4版本以上只能在vue3中使用,vue2需要执行npm i vue-router@3
) -
应用插件
Vue.use(VueRouter)
-
编写
router
配置项,并引入:src/router/index.js
该文件专门用于创建整个应用的路由器// 该文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' //引入组件 import About from '../components/About' import Home from '../components/Home' //创建并暴露一个路由器 export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] })
src/main.js
//引入Vue import Vue from 'vue' //引入App import App from './App.vue' //引入VueRouter import VueRouter from 'vue-router' //引入路由器 import router from './router' //关闭Vue的生产提示 Vue.config.productionTip = false //应用插件 Vue.use(VueRouter) //创建vm new Vue({ el:'#app', render: h => h(App), router:router })
src/App.vue
<template> <div> <div class="row"> <div class="col-xs-offset-2 col-xs-8"> <div class="page-header"><h2>Vue Router Demo</h2></div> </div> </div> <div class="row"> <div class="col-xs-2 col-xs-offset-2"> <div class="list-group"> <!-- 原始html中我们使用a标签实现页面的跳转 --> <!-- <a class="list-group-item active" href="./about.html">About</a> --> <!-- <a class="list-group-item" href="./home.html">Home</a> --> <!-- Vue中借助router-link标签实现路由的切换 --> <router-link class="list-group-item" active-class="active" to="/about">About</router-link> <router-link class="list-group-item" active-class="active" to="/home">Home</router-link> </div> </div> <div class="col-xs-6"> <div class="panel"> <div class="panel-body"> <!-- 指定组件的呈现位置 --> <router-view></router-view> </div> </div> </div> </div> </div> </template> <script> export default { name:'App', } </script>
-
实现切换
<router-link></router-link>
浏览器会被替换为a标签active-class
可配置高亮样式 -
指定展示位
<router-view></router-view>
几个注意事项
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息 - 整个应用只有一个
router
,可以通过组件的$router
属性获取到
多级路由
-
配置路由规则,使用
children
配置项export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News, }, { path:'message', component:Message, } ] } ] })
-
跳转(要写完整路径)
<router-link to="/home/news">News</router-link>
路由的query参数
-
传递参数
<!-- 跳转路由并携带query参数,to的字符串写法 --> <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> --> <!-- 跳转路由并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:m.id, title:m.title } }"> {{m.title}} </router-link>
-
接收参数
$route.query.id $route.query.title
命名路由
-
作用:可以简化路由的跳转
-
如何使用
-
给路由命名
export default new VueRouter({ routes:[ { name:'guanyu', path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News, }, { path:'message', component:Message, children:[ { name:'xiangqing', path:'detail', component:Detail, } ] } ] } ] })
-
简化跳转
<!-- 跳转路由并携带query参数,to的字符串写法 --> <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> --> <!-- 跳转路由并携带query参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', query:{ id:m.id, title:m.title } }"> {{m.title}} </router-link>
-
路由的 params
参数
-
配置路由,声明接收
params
参数export default new VueRouter({ routes:[ { name:'guanyu', path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News, }, { path:'message', component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', component:Detail, } ] } ] } ] })
-
传递参数
注意:路由携带
params
参数时,若使用to
的对象写法,则不能使用path
配置项,必须使用name
配置! -
接收参数
$route.params.id $route.params.title
路由的 props
配置
props
作用:让路由组件更方便的收到参数
export default new VueRouter({
routes:[
{
name:'guanyu',
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News,
},
{
path:'message',
component:Message,
children:[
{
name:'xiangqing',
path:'detail',
component:Detail,
//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
// props:{a:1,b:'hello'}
//props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。
// props:true
//props的第三种写法,值为函数
props(params) {
return {
id: params.id
title:params.title
}
}
}
]
}
]
}
]
})
路由跳转的replace方法
-
作用:控制路由跳转时操作浏览器历史记录的模式
-
浏览器的历史记录有两种写入方式:
push
和replace
push
是追加历史记录replace
是替换当前目录,路由跳转时候默认为push
方式 -
开启replace模式
<router-link :replace="true" ...>News</router-link>
简写
<router-link replace ...>News</router-link>
总结:浏览记录本质是一个栈,默认push
,点开新页面就会在栈顶追加一个地址,后退,栈顶指针向下移动,改为replace
就是不追加,而将栈顶地址替换。
编程式路由导航(不使用<router-link>
)
作用:不借助 <router-link>
实现路由跳转,让路由跳转更加灵活
this.$router.push({})
:内传的对象与<router-link>
中的to
相同
this.$router.replace({})
this.$router.forward({})
:前进
this.$router.back({})
:后退
this.$router.go(n)
:可前进也可后退,n为正数前进n,n为负数后退n
this.$router.push({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
缓存路由组件
Keep-alive 是什么
keep-alive
是vue
中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM
keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
作用:
include
- 字符串或正则表达式。只有名称匹配的组件会被缓存。exclude
- 字符串或正则表达式。任何名称匹配的组件都不会被缓存。max
- 数字。最多可以缓存多少组件实例
在组件中使用
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
匹配首先检查组件自身的 name
选项,如果 name
选项不可用,则匹配它的局部注册名称 (父组件 components
选项的键值),匿名组件不能被匹配
⚠️设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated
与deactivated
):
- 首次进入组件时:
beforeRouteEnter
>beforeCreate
>created
>mounted
>activated
> … … >beforeRouteLeave
>deactivated
- 再次进入组件时:
beforeRouteEnter
>activated
> … … >beforeRouteLeave
>deactivated
在路由中使用
<keep-alive include = "News"><router-view></router-view></keep-alive>
<keep-alive :include = "['News','Message']"><router-view></router-view></keep-alive>
// 缓存一个路由组件
<keep-alive include = "News">
<router-view></router-view>
</keep-alive>
// 缓存多个路由组件
<keep-alive :include = "['News','Message']">
<router-view></router-view>
</keep-alive>
使用场景
使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive
举个栗子:
-
当我们从
首页
–>列表页
–>商详页
–>再返回
,这时候列表页应该是需要keep-alive
-
从
首页
–>列表页
–>商详页
–>返回到列表页(需要缓存)
–>返回到首页(需要缓存)
–>再次进入列表页(不需要缓存)
,这时候可以按需来控制页面的keep-alive
例如:
<template> <div> <keep-alive :include="shouldCache ? ['home'] : []"> <!-- 列表页的内容 --> </keep-alive> </div> </template> <script> export default { computed: { shouldCache() { // 判断列表页的来源是否是首页 return this.$route.params.from !== 'home'; } } }; </script>
在路由中设置keepAlive
属性判断是否需要缓存
{
path: 'list',
name: 'itemList', // 列表页
component (resolve) {
require(['@/pages/item/list'], resolve)
},
meta: {
keepAlive: true,
title: '列表页'
}
}
使用<keep-alive>
<div id="app" class='wrapper'>
<keep-alive>
<!-- 需要缓存的视图组件 -->
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<!-- 不需要缓存的视图组件 -->
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
原理分析
keep-alive
是vue
中内置的一个组件
源码位置:src/core/components/keep-alive.js
export default {
name: 'keep-alive',
abstract: true,
props: {
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
max: [String, Number]
},
created () {
this.cache = Object.create(null)
this.keys = []
},
destroyed () {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
render() {
/* 获取默认插槽中的第一个组件节点 */
const slot = this.$slots.default
const vnode = getFirstComponentChild(slot)
/* 获取该组件节点的componentOptions */
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
/* 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag */
const name = getComponentName(componentOptions)
const { include, exclude } = this
/* 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode */
if (
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
/* 获取组件的key值 */
const key = vnode.key == null
// same constructor may get registered as different local components
// so cid alone is not enough (#3269)
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
/* 拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存 */
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key)
}
/* 如果没有命中缓存,则将其设置进缓存 */
else {
cache[key] = vnode
keys.push(key)
// prune oldest entry
/* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
可以看到该组件没有template
,而是用了render
,在组件渲染的时候会自动执行render
函数
this.cache
是一个对象,用来存储需要缓存的组件,它将以如下形式存储:
this.cache = {
'key1':'组件1',
'key2':'组件2',
// ...
}
在组件销毁的时候执行pruneCacheEntry
函数
function pruneCacheEntry (
cache: VNodeCache,
key: string,
keys: Array<string>,
current?: VNode
) {
const cached = cache[key]
/* 判断当前没有处于被渲染状态的组件,将其销毁*/
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
在mounted
钩子函数中观测 include
和 exclude
的变化,如下:
mounted () {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
}
如果include
或exclude
发生了变化,即表示定义需要缓存的组件的规则或者不需要缓存的组件的规则发生了变化,那么就执行pruneCache
函数,函数如下:
function pruneCache (keepAliveInstance, filter) {
const { cache, keys, _vnode } = keepAliveInstance
for (const key in cache) {
const cachedNode = cache[key]
if (cachedNode) {
const name = getComponentName(cachedNode.componentOptions)
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
}
}
}
在该函数内对this.cache
对象进行遍历,取出每一项的name
值,用其与新的缓存规则进行匹配,如果匹配不上,则表示在新的缓存规则下该组件已经不需要被缓存,则调用pruneCacheEntry
函数将其从this.cache
对象剔除即可
关于keep-alive
的最强大缓存功能是在render
函数中实现
首先获取组件的key
值:
const key = vnode.key == null?
componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
拿到key
值后去this.cache
对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存,如下:
/* 如果命中缓存,则直接从缓存中拿 vnode 的组件实例 */
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
/* 调整该组件key的顺序,将其从原来的地方删掉并重新放在最后一个 */
remove(keys, key)
keys.push(key)
}
直接从缓存中拿 vnode
的组件实例,此时重新调整该组件key
的顺序,将其从原来的地方删掉并重新放在this.keys
中最后一个
this.cache
对象中没有该key
值的情况,如下:
/* 如果没有命中缓存,则将其设置进缓存 */
else {
cache[key] = vnode
keys.push(key)
/* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
表明该组件还没有被缓存过,则以该组件的key
为键,组件vnode
为值,将其存入this.cache
中,并且把key
存入this.keys
中
此时再判断this.keys
中缓存组件的数量是否超过了设置的最大缓存数量值this.max
,如果超过了,则把第一个缓存组件删掉
缓存后如何获取数据
解决方案可以有以下两种:
- beforeRouteEnter
- actived
beforeRouteEnter
每次组件渲染的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){
next(vm=>{
console.log(vm)
// 每次进入路由执行
vm.getData() // 获取数据
})
},
actived
在keep-alive
缓存的组件被激活的时候,都会执行actived
钩子
activated(){
this.getData() // 获取数据
},
注意:服务器端渲染期间
avtived
不被调用
路由守卫
activated
和deactivated
activated
和deactivated
是路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
具体使用
activated
路由组件被激活时触发deactivated
路由组件失活时触发
路由守卫
作用:对路由进行权限控制
分类:
-
全局守卫
meta
路由源信息//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用 router.beforeEach((to,from,next)=>{ console.log('前置路由守卫',to,from) if(to.meta.isAuth){ //判断是否需要鉴权 if(localStorage.getItem('school')==='atguigu'){ next() }else{ alert('无权限查看!') } }else{ next() } }) //全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用 router.afterEach((to,from)=>{ console.log('后置路由守卫',to,from) if(to.meta.title){ document.title = to.meta.title //修改网页的title }else{ document.title = 'vue_test' } })
-
独享守卫
beforeEnter(to,from,next){ if(localStorage.getItem('school') === 'uestc'){ next() }else{ alert('暂无权限查看') } }
-
组件内守卫
//进入守卫:通过路由规则,进入该组件时被调用 beforeRouteEnter(to,from,next){...next()}, //离开守卫:通过路由规则,离开该组件时被调用 beforeRouteLeave(to,from,next){...next()}
路由器的两种工作模式
-
对于一个
url
来说,什么是hash
值#
及其后面的内容就是hash
值 -
hash
值不会包含在HTTP
请求中,即hash
值不会带给服务器 -
hash
模式- 地址中永远带着
#
号,不美观 - 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
- 兼容性较好
- 地址中永远带着
-
history
模式-
地址干净,美观
-
兼容性和
hash
模式相比略差 -
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
在node.js中,使用
connect-history-api-fallback
中间件,必须要在引入静态资源中间件之前引用,例如app.use(history())
-
const router = new VueRouter({
mode:'history',
routes:[..]
})
export default router