什么是路由?
路由是根据不同的url
地址展现不同的内容或页面。早期的路由都是后端直接根据url
来重载页面实现的,即后端控制路由。后来页面越来越复杂,服务器压力越来越大,随着ajax
(异步刷新技术)的出现,页面的实现非重载就能刷新数据,让前端也可以控制url
自行管理,前端路由由此而生。
什么时候使用前端路由?
前端路由更多用在单页应用上,也就是SPA(Single Page Web Application)
,在单页面应用中,大部分页面保持不变,只改变部分内容的使用。
安装路由
npm install vue-router
使用路由
import VueRouter from 'vue-router';
Vue.use(VueRouter);
// 可以从其他文件 import 进来
const Home = { template: 'Home page' }
const About = { template: 'about page' }
// 每个路由应该映射一个组件
const routes = [
{ path: '/home', component: Home },
{ path: '/about', component: About }
]
routes
配置const router = new VueRouter({
routes
})
const app = new Vue({
router
}).$mount('#app')
和
<div id="app">
<h1>Hello App!h1>
<ul>
<router-link to="/home">首页router-link>
<router-link to="/about" tag="li">关于router-link>
ul>
<router-view>router-view>
div>
注意
如果我们想要为当前选中的路由设置样式的话,就可以通过下面两个类名:
to
属性的值to
属性的值我们可以通过配置更改选中路由的 class 名
new VueRouter({
linkActiveClass: 'link-active',
linkExactActiveClass: 'link-exact-active',
})
hash模式:vue-router 默认 hash 模式,使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
history 模式:这种模式充分利用 history.pushState
API 来完成 URL 跳转而无须重新加载页面。
在路由配置中设置:
new VueRouter({
mode: 'history',
})
我们在项目中通常使用的是 history 模式,因为 hash 模式太丑啦。使用这种模式,我们通常会在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则返回一个页面,或跳转到首页。
可以通过一个名称标识一个路由,这样在某些时候会显得更方便一些,特别是在链接一个路由,或者是执行一些跳转时,可以在创建Router实例时,在routes配置中给某个路由设置名称:
routes = [
{
path: '/home',
name: 'Home',
component: Home,
}
];
要链接到一个命名路由,可以给 router-link
的 to 属性传一个对象:
<router-link :to="{ name: 'Home' }">首页router-link>
一个被 router-view 渲染的组件想要包含自己的嵌套 router-view 时,可以使用嵌套路由,通常的说法叫子路由,如:
{
path: '/about',
component: () => import('./views/About'),
children: [
{
path: '/about/us',
component: () => import('./views/Us'),
},
{
path: '/about/company',
component: () => import('./views/Company'),
}
],
}
经过这样的设置,在 About 组件中就可以使用 router-view 了。子路由的 path 可以简写:
{
path: '/about',
component: () => import('./views/About'),
children: [
{
path: 'us',
component: () => import('./views/Us'),
}
],
}
这样会自动将父路由的路径,拼接在子路由前,最终结果为:/about/us
。
当访问 /about
下的其他路径时,并不会渲染出来任何东西,如果想要渲染点什么,可以提供一个空路由:
{
path: '/about',
children: [
{
path: '',
component: () => import('./views/SomeComp'),
},
],
}
重定向也是通过 routes 配置来完成,通过 redirect
属性,下面例子是从根路径/
重定向到 /home
const router = new VueRouter({
routes: [
{
path: '/',
redirect: "/home"
},
{
path: '/home',
name: 'Home',
component: Home,
},
]
})
重定向的目标也可以是一个命名的路由:
const router = new VueRouter({
routes: [
{ path: '/', redirect: { name: 'Home' }}
]
})
甚至是一个方法,动态返回重定向目标:
const router = new VueRouter({
routes: [
{ path: '/', redirect: to => {
return {
path:"/home"
}
}}
]
})
注意:方法接收目标路由
作为参数,return
重定向的字符串路径/路径对象
“重定向” 的意思是,当用户访问根/
时,URL 将会被替换成 /home
,匹配路由为 /home
,
那么“别名”又是什么呢?
/home
的别名是 /
,意味着,当用户访问 /
时,URL 会保持为 /
,但是路由匹配则为 /home
,就像用户访问 /home
一样。
上面对应的路由配置为:
const router = new VueRouter({
routes: [
{ path: '/home', component: Home, alias: '/' }
]
})
通过在 Vue
根实例的 router 配置传入 router 实例,$router
、 $route
两个属性会被注入到每个子组件。
路由实例对象。除了使用
创建 a
标签来定义导航链接,我们还可以借助 router
的实例
方法,通过编写代码来实现。
想要导航到不同的 URL,则使用 router.push
方法。这个方法会向 history
栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL
。
当你点击
时,这个方法会在内部调用,所以说,点击
等同于调用 $router.push(...)
。
声明式 | 编程式 |
---|---|
|
this.$router.push(...) |
该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
// 字符串
this.$router.push('/home')
// 对象
this.$router.push({ path: '/home' })
// 命名的路由
this.$router.push({ name: 'home' })
跟 router.push
很像,唯一的不同就是,它不会向 history
添加新记录,而是替换掉当前的 history
记录。
声明式 | 编程式 |
---|---|
|
this.$router.replace(...) |
这个方法的参数是一个整数,意思是在 history
记录中向前或者后退多少步,类似 window.history.go(n)
。
// 在浏览器记录中前进一步,等同于 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)
只读,路由信息对象。当我们想要获取当前路由的信息,就可以使用这个实例对象
$route.path:字符串,对应当前路由的路径,总是解析为绝对路径,如 "/home/a"
。
$route.params:一个 key/value
对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
$route.query:一个 key/value
对象,表示 URL 查询参数。例如,对于路径 /detail?id=1
,则有 $route.query.id== 1
,如果没有查询参数,则是个空对象。
$route.hash:路由的 hash
值 (带 #
) ,如果没有 hash
值,则为空字符串。
$route.fullPath:完成解析后的 URL
,包含查询参数和 hash
的完整路径。
$route.matched:一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是 routes
配置数组中的对象副本 (还有在 children
数组)。将会是一个包含从上到下的所有对象 (副本)。
$route.name:当前路由的名称,如果没有则是空字符串
$route.redirectedFrom:如果存在重定向,即为重定向来源的路由的名字。
举个例子:
当我们需要把某种模式匹配到的所有路由,全都映射到同个组件。就像csdn上博客文章https://blog.csdn.net/Newbie___/article/details/105480827
,我们来模仿一下,我们有一个 Detail
组件,对于所有 ID
各不相同的文章,都要使用这个组件来渲染。那么,我们可以在 vue-router
的路由路径中使用 “ 动态路径参数 ” 来达到这个效果:
const Detail = {
template: 'Detail'
}
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/details/:id', name:"Details", component: Detail }
]
})
经过这样的设置,像 /details/123
和 /details/456
都将映射到相同的路由。
一个“路径参数” 使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params
,可以在每个组件内使用。
使用方式一:
this.$router.push({ path: `/details/${this.product.id}` });
使用方式二:当然我们的id应该是动态的,但我这里就不拼接了,使用一个静态id
<router-link :to="{name:'Details',params:{id:12345}}">click merouter-link>
使用方式三:
<router-link :to="{path:'/details/12345'}">click merouter-link>
想同时展示多个视图时,并且每个视图展示不同的组件时,可以使用命名视图。
可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view
没有设置名字,那么默认为 default
。
<router-view class="view one">router-view>
<router-view class="view two" name="a">router-view>
<router-view class="view three" name="b">router-view>
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components
配置:
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
在组件中使用 $route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。我们可以使用使用 props
将组件和路由解耦。
如果 props
被设置为 true
,route.params
将会被设置为组件属性。
const routes = [
//...
{
path: '/details/:id',
name: 'Details',
props:true,
component: () => import('@views/ProductDetails.vue')
}
]
在页面中,我们就可以接受属性,如 id:
props:{
id:{
type:[String,Number]
}
},
如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用。
const router = new VueRouter({
routes: [
{
path: '/details/:id',
name: 'Details',
props: {
name: "detail-page"
},
component: () => import('@views/ProductDetails.vue')
}
]
})
你可以创建一个函数返回 props。函数的第一个参数是 route (即$route
)。
const router = new VueRouter({
routes: [
{
path: '/details/:id',
name: 'Details',
props:(route)=>{
return {
id:route.params.id,
name:route.name
}
},
component: () => import('@views/ProductDetails.vue')
}
]
})
props:{
id:{
type:[String,Number]
},
name:{
type:String
}
}
什么是导航守卫呢?导航说的是路由正在发生变化,导航守卫就是通过跳转或取消的方式来守卫导航。导航守卫被分成三种:全局的、单个路由独享的、组件内的。
在导航守卫中一般都会接收这三个参数:
next()
,才能继续往下执行一个钩子,否则路由跳转会停止next(false)
。next('/home')
的方式跳转到一个不同的地址。终止当前导航,进入一个新的导航。next参数值和$routet.push
一致。next(error)
。2.4+,如果传入的参数是一个 Error 实例,则导航会被终止,且该错误会被传递给router.onError()
注册过的回调。举个例子:to 和 from 对象的内容(与上面$route
内容相同)
全局守卫是指路由实例上直接操作的钩子函数,触发路由就会触发这些钩子函数。
在路由跳转前触发,一般被用于登录验证。
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
和 boforeEach 类似,路由跳转前触发。和 beforeEach 的区别:在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
const router = new VueRouter({ ... })
router.beforeResolve((to, from, next) => {
// ...
})
和 beforeEach 相反,路由跳转完成后触发。
const router = new VueRouter({ ... })
router.afterEach((to, from) => {
// ...
})
是指在单个路由配置的时候也可以设置的钩子函数。
和beforeEach完全相同,如果都设置则在beforeEach之后紧随执行。进入该路由时才起作用
const router = new VueRouter({
routes: [
{
path: '/home',
component: Home,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内守卫是指在组件内执行的钩子函数,类似于组件内的生命周期,相当于为配置路由的组件添加的生命周期钩子函数。
在该守卫内访问不到组件的实例,this 值为 undefined。在这个钩子函数中,可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数,可以在这个守卫中请求服务端获取数据,当成功获取并能进入路由时,调用 next 并在回调中通过 vm 访问组件实例进行赋值等操作,(next中函数的调用在 mounted 之后:为了确保能对组件实例的完整访问)。
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
next( vm => {
// 通过 `vm` 访问组件实例
})
},
何时组件会被复用?只有当动态路由间互相跳转,路由query变更时会被复用
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /details/:id,在 /details/1 和 /details/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
// 全局守卫
router.beforeEach((to,from,next)=>{
console.log("beforeEach守卫")
console.log(to,from)
next()
})
router.beforeResolve((to,from,next)=>{
console.log("beforeResolve守卫")
console.log(to,from)
next()
})
router.afterEach((to,from)=>{
console.log("afterEach")
console.log(to,from)
})
beforeRouteEnter (to, from, next) {
// ...
console.log(to,from)
console.log("beforeRouteEnter")
next(vm=>{
console.log(vm)
})
},
beforeRouteUpdate(to,from,next){
// ...
console.log(to,from)
console.log("beforeRouteUpdate")
console.log(this)
next()
}
beforeRouteLeave (to, from, next) {
// ...
console.log(to,from)
console.log("beforeRouteLeave")
next()
}
beforeRouteLeave
。beforeEach
守卫。beforeRouteUpdate
守卫 (2.2+)。beforeEnter
。beforeRouteEnter
。beforeResolve
守卫 (2.5+)。afterEach
钩子。beforeRouteEnter
守卫中传给 next 的回调函数。我举个例子,我在/home
跳转到/details/12345
,我们来看看依次发生了什么
想要触发组件内守卫 beforeRouteLeave
,需要在离开的路由地址的组件中设置
//Home.vue
beforeRouteLeave (to, from, next) {
// ...
console.log("beforeRouteLeave守卫触发")
next()
},
在目标路由的组件内配置 组件内守卫 beforeRouteEnter
和 beforeRouteUpdate
//Details.vue
beforeRouteEnter (to, from, next) {
// ...
console.log("beforeRouteEnter守卫触发")
next(vm=>{
console.log(vm.name) //在该组件内data中有一个变量 name:"young monk"
})
},
beforeRouteUpdate(to, from, next){
// ...
console.log("beforeRouteUpdate守卫触发")
next()
}
在全局路由中配置全局守卫:
//index.js(路由配置的js)
const router = new VueRouter({
//...
})
router.beforeEach((to,from,next)=>{
// ...
console.log("全局beforeEach触发")
next()
})
router.beforeResolve((to,from,next)=>{
// ...
console.log("全局beforeResolve触发")
next()
})
router.afterEach((to,from)=>{
// ...
console.log("全局afterEach触发")
})
最后依次输出的结果是:
beforeRouteLeave守卫触发
全局beforeEach触发
路由独享守卫beforeEnter触发
beforeRouteEnter守卫触发
全局beforeResolve触发
全局afterEache触发
young monk
按照上面的完整的导航解析流程,这个结果就并不意外了,唯独组件内守卫 beforeRouteUpdate 没有触发,是因为组件没用被复用。
定义路由的时候可以配置 meta
字段,用于自定义一些信息。
const router = new VueRouter({
routes: [
{
path: '/home',
component: Home,
meta: { isLogin: true }
}
]
})
是基本的动态组件,所以我们可以用
组件给它添加一些过渡效果。
<transition>
<router-view>router-view>
transition>
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。vue-router 可以自定义路由切换时页面如何滚动。
注意: 这个功能只在支持 history.pushState
的浏览器中可用。
当创建一个 Router
实例,你可以提供一个 scrollBehavior
方法:
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
}
})
参数:
{ x: 0,y: 0 }
。当且仅当 popstate
导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。返回值:
{ x: number, y: number }
{ selector: string, offset? : { x: number, y: number }}
(offset 只在 2.6.0+ 支持)scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 } //回到顶部
}
scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return {
selector: to.hash // selector 的 值为 hash值
}
}
}
位置,就像重新加载页面那样。vue-router 可以自定义路由切换时页面如何滚动。
注意: 这个功能只在支持 history.pushState
的浏览器中可用。
当创建一个 Router
实例,你可以提供一个 scrollBehavior
方法:
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
}
})
参数:
{ x: 0,y: 0 }
。当且仅当 popstate
导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。返回值:
{ x: number, y: number }
{ selector: string, offset? : { x: number, y: number }}
(offset 只在 2.6.0+ 支持)scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 } //回到顶部
}
scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return {
selector: to.hash // selector 的 值为 hash值
}
}
}