一、简介
在 html 中,使用 a 元素进行页面的跳转,如<a href="https://www.baidu.com">Baidu</a>
。
在 Vue 中,则使用 Vue Router 进行页面的跳转。
参考了Vue Router 教程和Vue Router API。
二、使用Vue Router
使用npm install vue-router
下载 Vue Router。
在项目中新建一个 router.js 的文件,里面书写所有路由的配置。
当然,也可以将这些配置全部写在 main.js 中,建议分开使用。
import VueRouter from 'vue-router'
import Router1 from './Router1.vue'
import Router2 from './Router2.vue'
export default new VueRouter({
routes: [
{ path: '/', component: Router1 },
{ path: '/aa', component: Router2 }
]
})
使用import
引入 VueRouter 和要跳转的界面,再使用new VueRouter({})
创建路由器实例,通过export default
将实例导出。
routes
选项是一个数组,数组元素是路由。路由对象也可以配置不同的key,path
为路由的路径,component
为路由绑定的组件。还有其他的key,将在下面介绍。
在 main.js 中将上面导出的路由器实例添加到 Vue 实例中。
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import router from "./router.js"
Vue.config.productionTip = false
Vue.use(VueRouter)
new Vue({
el: "#app",
render: h => h(App),
router: router
})
使用import
引入路由器实例和 VueRouter,使用Vue.use(VueRouter)
来使用插件,在new Vue({})
中添加router
选项,这样 Vue 就有了路由的功能。
首页是渲染在组件 APP 上的,在组件 APP 中则要渲染路由绑定的组件。
<template>
<div id="div">
<router-view></router-view>
<router-link to="/aa"></router-link>
</div>
</template>
router-view 元素是个 functional 组件,它会显示对应路由绑定的组件。
因为组件 APP 是首页,所以默认会显示path="/"
的路由绑定的组件。此时<router-view></router-view>
相当于<Router1></Router1>
。
可通过new Router({ })
中的base
选项来设置应用的基路径,默认为/
。
router-link 元素相当于一个 a 元素,点击后会跳转到path="/aa"
的路由绑定的组件。
- 命名路由
可在路由中添加name
给一个路由命名,如{ path: '/aa', component: Router2, name: "aa" }
。
命名路由可使用<router-link :to="{ name: 'aa' }"></router-link>
来跳转,to
前要使用冒号:
。
三、动态路由
- 动态路径参数
上面的路径是固定的,有时候我们需要根据传入的不同参数来显示不同的界面,可以使用动态路径参数。
使用:
来添加参数,如{ path: "/aa/:id",component: Router2 }
。aa
为路径名,id
为参数名。
通过<router-link to="/aa/zhangsan123"></router-link>
来设置参数值。
在组件 Router2 中,使用this.$route.params
来获取已设置的参数,此时为{ "id": "zhangsan123" }
。
- 多参数
使用{ path: "/aa/:id/:age",component: Router2 }
来添加多个参数。
通过<router-link to="/aa/zhangsan123/30"></router-link>
来设置参数值,此时参数值为{ "id": "zhangsan123", "age": "30" }
。 - 命名路由
若路由是一个命名路由,如{ path: "/aa/:id/:age",component: Router2, name: "aa" }
。
可通过<router-link :to="{ name: 'aa', params: { id: 'zhangsan123', age: '30' } }"></router-link>
来设置参数值,此时参数值为{ "id": "zhangsan123", "age": "30" }
。
通配符
使用通配符*
来设置路径,如{ path: "*",component: Router2 }
会匹配任意路径,通常用在404界面。
使用通配符*
来设置部分路径,如{ path: "/aa*",component: Router2 }
会匹配前缀为/aa
的路径,如<router-link to="/aa123"></router-link>
。
使用通配符的路由,this.$route.params
中会多一个keypathMatch
。当路径为/aa123
时this.$route.params
为{ "pathMatch": "123" }
。
注:使用通配符的应该放在routes
选项的最后面,因为越往前优先级越高。监听跳转
有一个路由{ path: "/aa/:id",component: Router2 }
,当从/aa/zhangsan123
跳转到/aa/lisi456
时,原来的 Router2 组件会复用,这会大大减少渲染开销。
但是,这会导致组件 Router2 的生命周期钩子函数不会被触发,此时,可使用组件export default {}
中的watch
选项监听$route
的变化从而监听路由跳转。
watch: {
$route: function(to, from) {
var a = from.params
var b = to.params
}
}
to from
都是路由对象,表示要进入/离开的路由。
此时两个路由是相同的,可使用from.params、to.params
获取动态路径参数来判断。
也可使用组件内守卫beforeRouteUpdate
来监听跳转。(守卫相当于钩子)
beforeRouteUpdate(to, from, next) {
var a= from.params
var b = to.params
next()
}
to from
都是路由对象,next()
表示继续触发下一个守卫,必须调用。
- 组件props绑定
有一个路由{ path: "/aa/:id",component: Router2 }
,可以在组件 Router2 中使用this.$route.params.id
获取参数值。
在路由中添加props
,如{ path="/aa/:id",component: Router2, props: true }
,此时路由的 id 参数绑定的值会自动绑定至组件 Router2 的自定义属性 id 上, 组件就可以使用this.id
获取参数值。
注:组件 Router2 的export default {}
中也需要添加props: ["id"]
。
- props的类型
props
可为布尔、对象和函数。- 为布尔时,如上。
- 为对象时,可用在命名视图中,详见 五、命名视图 。
如{ path: '/ee/:id', component: Router3, components: { default: Router5, a: Router6 }, props: { default: true, a: true } }
,在组件 Router5 和 Router6 中都能使用id
获取参数值。 - 为对象时,对象还可为静态值,如
{ path: "/aa",component: Router2, props: { adress: "chengdu" } }
,在组件 Router2 中可使用adress
获取参数值。 - 为函数时,返回一个对象,对象为静态值。如
{ path: "/aa",component: Router2, props: route => { return { adress: "chengdu"} } }
,route
为路由对象。
四、嵌套路由
上面介绍的是将一个路由当做一个新的页面,但也可以将一个路由作为一个组件,这个路由就是嵌套路由。
使用嵌套路由
在路由中添加children
,children
是一个数组,数组元素是也是路由,如{ path: '/bb', component: Router3, children: [{ path: 'cc', component: Router4 }] }
。
在组件 Router3 中需要嵌套的地方添加<router-view></router-view>
。
通过<router-link to="/bb/cc"></router-link>
就可以跳转到使用了嵌套路由cc的路由/bb上。
注:children
数组中的路由的path
不需要使用/
。嵌套路由的用法
根据上面介绍的,实际上可以将组件 Router3 的<router-view></router-view>
,直接替换成<Router4></Router4>
,两者作用是相同的。
- 嵌套路由的直正用法是根据嵌套路由的
path
不同,来匹配不同的界面。
如{ path: '/bb', component: Router3, children: [{ path: 'cc', component: Router4 }, { path: 'dd', component: Router5 }] }
。
组件 Router3 模板中只需要一句代码<router-view></router-view>
。
当使用<router-link to="/bb/cc"></router-link>
就会跳转到组件 Router4 的页面,使用<router-link to="/bb/dd"></router-link>
就会跳转到组件 Router5 的页面。
-
使用动态路径参数
你会发现,使用了嵌套路由,会匹配/bb/cc
;若使用了动态路径参数,也可能会匹配/bb/cc
,cc是路径为/bb
的路由的参数。
若同时使用了嵌套路由与动态路径参数,它们会如何匹配。
若有一个路由{ path: '/bb/:id', component: Router3, children: [{ path: 'cc', component: Router4 }] }
。- 使用
<router-link to="/bb/cc"></router-link>
时,cc是参数id
的值,此时未使用嵌套路由。 - 使用
<router-link to="/bb/zhangsan/cc"></router-link>
时,zhangsan是参数id
的值,cc表示嵌套路由的路径。此时组件 Router3 和 Router4 的this.$route.params
的值都为{ "id": "zhangsan"}
。
当路径为
/bb
的路由有多个动态路径参数时,如{ path: /bb/:id/:age }
。此时会匹配/bb/zhangsan/20/cc
,总之是按照顺序匹配的。 - 使用
-
嵌套路由中使用动态参数
若有一个路由为{ path: '/bb/:id', component: Router3, children: [{ path: 'cc/:age', component: Router4 }] }
。- 使用
<router-link to="/bb/zhangsan/cc/20"></router-link>
时,zhangsan是id
的值,cc是嵌套路由的路径,20是age
的值。此时组件 Router3 和 Router4 的this.$route.params
的值都为{ "id": "zhangsan", "age": "20"}
。
总之,是按照顺序进行匹配的,且路由与嵌套路由的
this.$route.params
都相同。 - 使用
五、命名视图
上面的嵌套路由是在页面只使用一个组件,若要在页面上使用多个组件,则需要命名视图。
- 使用方式
因为要在页面上添加多个组件,所以要在页面上添加多个<router-view></router-view>
。
使用name
属性为 router-view 元素命名,如<router-view name="a"></router-view>
,未设置时默认为default
。
在该页面的路由中使用components
(后面有s),它是一个对象,对象的键为 router-view 元素name
属性的值。如{ path: '/cc', component: Router3, components: { default: Router5, a: Router6 } }
。
此时,组件 Router3 中的<router-view></router-view>
会被替换成 Router5,<router-view name="a"></router-view>
会被替换成 Router6。
通过<router-link to="/cc"></router-link>
进行跳转。
//在Router3的模板中
<template>
<div>
<router-view class="header"></router-view>
<router-view name="a" class="bottom"></router-view>
</div>
</template>
//在 router.js 中
import Router3 from './Router3.vue'
import Router5 from './Router5.vue'
import Router6 from './Router6.vue'
export default new VueRouter({
routes: [
{ path: '/cc', component: Router3, components: {
default: Router5
a: Router6
}}
]
})
六、导航
- router-link
上面都是使用<router-link to=""></router-link>
来进行导航跳转的。
router-link 元素默认会渲染成 a 元素,绑定点击事件click。
可使用tag
属性将渲染成其他元素,如<router-link to="" tag="div"></router-link>
。
可使用event
属性将点击事件修改为其他事件,如<router-link to="" event="dbclick"></router-link>
。
- 使用插槽
router-link 元素也可以使用插槽与作用域插槽。
router-link 元素默认情况下相当于下面。
<router-link to="/aa" custom v-slot="datas">
<a :href="datas.href" @click="datas.navigate">{{ datas.route.fullPath }}</a>
</router-link>
参数datas中有几种数据:
href
解析后的 URL,会赋值给 a 元素的 href
属性。
route
路由对象。
navigate
触发导航的函数,函数里面会调用this.$router.push()
。
isActive
为true表示链接激活时会使用类名,类名默认router-link-active
。
可使用 router-link 元素的active-class
属性修改,也使用new Router({})
中的linkActiveClass
选项来全局配置。
isExactActive
为true表示精确匹配的链接激活时会使用类名,类名默认router-link-exact-active
。
可使用 router-link 元素的exact-active-class
属性修改,也使用new Router({})
中的linkExactActiveClass
选项来全局配置。
注:将 router-link 元素的exact
属性设为true可变成精确匹配。
-
push
可以给一个元素绑定一个点击事件,在点击事件中调用$router.push()
来跳转页面,如<div @click="$router.push('/aa/zhangsan')">router</div>
。
this.$router.push(location, onComplete, onAbort)
会向 history 中添加一条新记录,点击浏览器中的后退按钮可以返回之前的页面,相当于window.history.pushState()
。
有三个参数:-
location
可为字符串或对象,是必填值。与 router-link 元素的to
属性的取值相同。- 为字符串时,表示路由的路径,如
this.$router.push("/aa/zhangsan")
会匹配/aa/zhangsan。 - 为对象时,对象中的
path
表示路径,query
表示请求参数,如this.$router.push({ "path": "/aa", query: { "id": "zhangsan" }})
会匹配/aa?id=zhangsan。 - 为对象时,还可使用
name
与params
,name
表示路由的名称,params
是个对象,表示路由的动态路径参数。
如this.$router.push({ "name": "aa", params: { "id": "zhangsan" }})
会匹配/aa/zhangsan。
注:若使用了path
,那么params
会被忽略。
- 为字符串时,表示路由的路径,如
-
onComplete
是一个回调函数,会在导航成功完成时调用。 -
onAbort
是一个回调函数,会在导航终止时 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由)调用。
回调函数有一个参数 故障信息failure
,可使用failure.to、failure.from
获取两个路由对象,详见导航故障。
-
replace
this.$router.replace()
也能跳转页面,只不过不会向 history 中添加新记录,而是会替换当前的记录,相当于window.history.replaceState()
。
参数与push()
参数相同。
replace()
也与<router-link to="" replace></router-link>
功能相同。操作history
this.$router.back()
会后退一个页面,相当于window.history.back()
。
this.$router.forward()
会前进一个页面,相当于window.history.forward()
。
this.$router.go()
会前进或后退多个页面,相当于window.history.go()
。this.$router.go(1)
相当于this.$router.forward()
,this.$router.go(-1)
相当于this.$router.back()
。
当window.history.go(-100)
时若历史记录不够,则不会有效果。
七、重定向与别名
- 重定向
重定向的作用是当点击<router-link to="/ff"></router-link>
时,实际是跳转到路径为/gg的路由。
在路由中添加redirect
,如{ path: '/ff', component: Router5, redirect: "/gg" }
。component
可以不写,因为跳转到了/gg,Router5 并不会使用。
注:路径为/gg的路由需要添加到routes
选项中。
-
类型
redirect
可为字符串、对象、函数。- 为字符串时,表示路径
path
的值。 - 为对象时,对象中的
name
为命名路由的名称,如{ path: '/ff', redirect: { name: "gg" } }
。 - 为函数时,函数返回字符串或对象,如
{ path: '/ff', redirect: route => { return "/gg" } }
。
注:重定向的路由需要与原路由的动态参数相同,如
{ path: '/ff/:id', redirect: "/gg/:id" }
。 - 为字符串时,表示路径
- 别名
别名的作用是点击<router-link to="/ff"></router-link>
后,新页面显示的 URL 不是/ff,而是/gg,但路由的路径实际还是/ff。
在路由中添加alias
,如{ path: '/ff', component: Router5, alias: "/gg" }
。
通常使用别名来自由地配置显示的 URL。
八、导航守卫
导航 表示路由正在发生改变,如/bb跳转到/cc。守卫相当于钩子,会在某条件下触发,但多了个next()
回调。
- 全局守卫或钩子
全局守卫或钩子通常用在new VueRouter({})
下面。
const router = new VueRouter({ routes: [] })
router.beforeEach((to, from, next) => {})
export default { router }
- 前置守卫
当一个导航触发时,全局前置守卫beforeEach((to, from, next) => { })
会按照创建顺序调用。守卫是异步解析执行的,导航会在所有守卫 resolve 前一直等待,resolve 是指调用回调函数next()
。
有三个参数:-
to
即将要进入的路由。 -
from
即将要离开的路由。 -
next
回调函数,必须要调用回调函数来 resolve 守卫,不调用会导致守卫永远都不会被解析或报错。
回调函数可传入参数:-
next()
继续触发下一个守卫。如果所有守卫都触发了,则导航的状态就是 confirmed (确认的)。 -
next(false)
中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动改变或者点击浏览器后退按钮),那么 URL 地址会重置到from
路由的 URL 地址。 -
next('/aa')
、next({ path: '/aa' })
跳转到另一个路由。当前的导航被中断,然后进行一个新的导航,会重新触发beforeEach
守卫。
此时next()
传入的参数与this.$router.push()
的第一个参数相同。 -
next(error)
传入的参数是一个Error
实例,导航会终止且错误会传递给router.onError()
中的回调函数。
-
-
- 解析守卫
在所有组件内守卫和异步路由组件被解析之后,全局解析守卫beforeResolve((to, from, next) => {})
就会触发。
参数与守卫beforeEach
相同。 - 后置钩子
导航状态变为确认后,全局后置钩子afterEach((to, from) => {})
就会触发。参数与守卫beforeEach
相同,但少了一个next()
回调。
- 路由内守卫
beforeEnter
守卫会在导航进入守卫beforeRouteEnter
前触发,参数与守卫beforeEach
相同。
该守卫用在路由内部。
new VueRouter({ routes: [
{ path: '/aa', component: Router1, beforeEnter: (to, from, next) => {} }
] })
- 组件内守卫
组件内守卫是写在组件内export default {}
的。
export default {
beforeRouteEnter(to, from, next) {
}
}
- 导航进入路由守卫
导航开始时,导航会离开当前路由,进入到新路由,新路由的组件会触发beforeRouteEnter(to, from, next)
守卫。
通常在该守卫中调用接口获取数据,获取成功后调用next()
。
参数与守卫beforeEach
相同。
注:该路由中不能访问组件实例this
,因为此时实例还未创建。但是,可以在该守卫中使用next(vue => { })
,vue就是组件实例,会在导航状态变为确认后触发回调。 - 路由更新守卫
一个使用了动态路径参数的路由{ path: "/aa/:id", component: Router }
,当从/aa/zhangsan跳转到/aa/lisi时,Router 组件会重用,此时就会触发beforeRouteUpdate(to, from, next)
守卫。
参数与守卫beforeEach
相同。 - 导航离开路由守卫
导航开始时,导航会离开当前路由,进入到新路由,当前路由的组件会触发beforeRouteLeave(to, from, next)
守卫。
离开守卫通常用来禁止用户在还未保存修改前突然离开,使用next(false)
来取消导航。
参数与守卫beforeEach
相同。
- 导航解析流程
- 导航被触发。
- 在当前路由的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫。 - 在
new VueRouter({ })
里调用beforeEnter
守卫。 - 解析异步路由组件。
- 在新路由的组件里调用
beforeRouteEnter
守卫。 - 调用全局的
beforeResolve
守卫。 - 导航状态变为确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫next( vue => { })
中的回调函数,组件实例会传递给回调函数。
九、滚动
当从路由切换到新的路由上时,想要页面滚动到顶部。当返回上一个页面时,想要页面滚动到原先的滚动位置。
可使用scrollBehavior
选项实现该功能,该功能可以在支持history.pushState()
的浏览器中使用。
export default new VueRouter({
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
})
有三个参数:
-
to
即将进入的路由。 -
form
即将离开的路由。 -
savedPosition
即将进入的路由原先的滚动位置。
需要返回一个对象:
-
x、y
向右/下滚动的距离,如return { x: 0, y: 0 }
。 -
selector
要滚动到的路由锚点,如return { selector: to.hash }
。 -
behavior
滚动模式,如return { selector: to.hash, behavior: "smooth" }
表示平滑地滚动到锚点位置。
也可以返回一个 Promise,可在里面的回调中调用一些异步方法(调接口、延迟操作等)
scrollBehavior (to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ x: 0, y: 0, behavior: "smooth" })
}, 500)
})
}