vue-router可以大幅度提升网站性能。只要首页加载完毕,在不同页面之间的导航就会变得飞快,因为所有的HTML、Javascript以及CSS都已经被下载下来了。
建议浏览一下核心概念再开始使用哦!
vue-router默认使用URL hash来存储路径。自从有了HTML5 history API,就不用非得跳转一个新页面来访问。
在创建router对象时,添加设置mode: 'history'
,即可。
若要在路由中添加动态参数,例如:/user/1234(userId)
,就要使用动态路由了:
vue-router
支持动态路径匹配,也就是说可以通过使用一种专门的语法来指定路径规则,而所有匹配到该规则的路由下的组件都能被访问到。
在路由表中设置path:"/user/:userId"
即可。
vue-router使用path-to-regexp库来实现这项功能,Express和Koa以及react都使用这种语法。
在组件实例中,想要获取动态参数,首先通过this.$route
获取当前路由对象,它的params
即动态参数列表:
{
”userId“: ”1234“
}
这里拿到的是字符串,要想当做数值使用,则需使用parseFloat或Number自行转换
当然,多段动态内容也是可以的:
"/user/:userId/page/:pageId"
在使用动态路由后,动态参数发生变化时,由于组件被复用,因此mounted并不会被调用,这时就需要用到beforeRouteUpdate导航守卫(guard):
<template>
<div v-if="state === 'loading'">
Loading user…
</div>
<div>
<h1>User: {{ userInfo.name }}</h1>
... etc ...
</div>
</template>
<script>
export default {
data: () => ({
state: 'loading',
userInfo: undefined
}),
mounted() {
this.init();
},
beforeRouteUpdate(to, from, next) {
this.state = 'loading';
this.init();
next();
},
methods: {
init() {
fetch(`/api/user/${this.$route.params.userId}`)
.then((res) => res.json())
.then((data) => {
this.userInfo = data;
}); }
}
};
</script>
一些页面初始化的内容抽出到init()方法,在mounted和beforeRouteUpdate都调用这个方法。代码复用,合理解耦。
vue2.2之前使用watch来监听路由变化:
watch: {
$route(to, from) {
// 对路由变化作出响应...
}
}
在由路由控制的子页面中,部分内容还需要一层路由控制时,可以使用嵌套路由,这时的路由对象可以设置为:
const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
}
]
}
]
})
组件(这时的组件其实是显示在另一个“父”<router-view>
中的):
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
要注意:
/
,只有根路由才带;{ path: "", component: UserHome }
若是想要在访问根路径时,看到的是网站首页或是在访问空白子路由时,看到第一个子路由中的内容则可以用到路由重定向redirect
来代替component
:
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/Home' },
{
path: '/user/:id',
component: User,
children: [
{
path: '',
redirect: 'profile',
},
{
path: 'profile',
component: UserProfile
},
{
path: 'posts',
component: UserPosts
}
]
}
]
})
若要将多个路由指向同一组件也可使用重定向
要想使用重定向更“隐蔽”一些(重定向后路由不变,照常显示),可以使用别名alias
:
const router = new VueRouter({
routes: [
{
path: '/Home',
component: Home,
alias: '/' },
{
path: '/user/:id',
component: User,
children: [
{
path: 'profile',
component: UserProfile,
alias: '',
},
...
]
}
]
})
当别名有多个时,可以设置为数组。
官方实例:https://github.com/vuejs/vue-router/blob/dev/examples/route-alias/app.js
传统使用<a>
标签来进行链接,这样会重新加载整个页面,对于SPA来说并不友好,因此使用<router-link>
来代替<a>
标签:
<router-link to="/bar">Go to Bar</router-link>
默认情况下会渲染为:
<a href="/bar">Go to Bar</a>
tag
属性若要想让它渲染为<li>
,这时需要用到tag
属性:
<router-link to="/bar" tag="li">Go to Bar</router-link>
这样会渲染为:
<li>Go to Bar</li>
为了让页面更加友好,使<a>
依然存在,可写为:
<router-link to="/bar" tag="li"><a>Go to Bar</a></router-link>
将会渲染为:
<li><a href="/bar">Go to Bar</a></li>
当<router-link>
组件的to
属性中的路径与当前页面的路径相匹配时,生成元素处于激活状态(active)。
这时,vue-router会自动为生成的元素赋予一个class
属性,默认为router-link-active
,也可以用active-class属性来配置class
的属性值。
例如:
<ul>
<router-link to="/blog" tag="li" active-class="active">
<a>Blog</a>
</router-link>
<router-link to="/user/1234" tag="li" active-class="active">
<a>User #1234</a>
</router-link>
</ul>
处于/blog
时,渲染为:
<ul>
<li class="active"><a href="/blog">Blog</a></li>
<li><a href="/user/1234">User #1234</a></li>
</ul>
若要为<router-link>
绑定事件需要使用@click.native
而非直接@click
<!-- 这 样 不 行 -->
<router-link to="/blog" @click="handleClick">Blog</router-link>
<!-- 这 样 可以 -->
<router-link to="/blog" @click.native="handleClick">Blog</router-link>
效仿浏览器原生的history方法——如history.pushState()、history.replaceState()以及history.go():
to
属性调用的就是router.push()
与router.push()
的区别是:
router.push()
会向history
栈添加一个新的记录——因此,如果用户离开后按下返回键,路由器就会跳转到上一个路由;在历史记录中前进和后退,就像按了前进键和后退键。后退一条记录,就用router.go(-1),而前进10条记录,就用router.go(10)。若历史中没那么多条记录,函数的调用就会终止。
<router-link>
在不同模式下表现也是不同的,在hash
模式下,前面的链接会带你前往#/user/1234
,而使用history
模式的话,则会带你去向/user/1234
。- 默认渲染生成的
<a>
标签的href
属性不仅能使悬停时显示跳转链接,还能使用新窗口来打开,这个vue-router就无法做到了。- 无论
<router-link>
最终被渲染为什么标签,vue都会给它绑定点击监听事件
beforeEach在导航发生之前运行,并且可设置取消导航或将用户导航至其他地方。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
每个守卫方法一般会接收三个参数:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
from和to分别表示导航从哪里来和到哪里去,而next则是一个回调,在里面你可以让vue-router去处理导航、取消导航、重定向到其他地方或者注册一个错误:
- to: Route: 即将要进入的目标 路由对象
- from: Route: 当前导航正要离开的路由
- next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
- next(): 通过。进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
- 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() 注册过的回调。
注:确保 next 函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。
例:
router.beforeEach((to, from, next) => {
if (to.path.startsWith('/account') && ! userAuthenticated())
next('/login');
} else {
next();
}
});
除了beforeEach
还有router.beforeResolve
和router.afterEach
。
router.beforeResolve
在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,被调用。router.afterEach
没有next
参数。虽然不会影响导航,但对于做些诸如设置页面标题的小事,也不乏实用之处:const router = new VueRouter({
routes: [
{
path: '/blog',
component: PageBlog,
meta: {
title: 'Welcome to my blog! '
}
}
]
});
router.afterEach((to) => {
document.title = to.meta.title;
});
还有可在路由表中直接定义的路由独享守卫:
const router = new VueRouter({
routes: [
{
path: '/account',
component: PageAccount,
beforeEnter(to, from, next) {
if (! userAuthenticated()) {
next('/login');
} else {
next();
}
}
}
]
});
beforeEnter守卫与beforeEach表现完全一致,只不过是单个路由独有。
beforeEach
、beforeResolve
和afterEach
是全局导航守卫,还有三个局部导航守卫,功能基本类似:
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
// 但是,可以在next里传一个回调,该回调会被传入组件实例并作为其第一个参数:
next((vm) => {
console.log(vm.$route);
});
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用(此时并不会调用mounted)
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
由于在beforeRouteUpdate和beforeRouteLeave里都能使用this,就像在组件内的其他大部分地方一样,因此这两个守卫并不支持在next里传入一个回调。
路由元信息设置在路由表中路由项的meta属性。改良之前的写法:
const router = new VueRouter({
routes: [
{
path: '/account',
component: PageAccount,
meta: {
requiresAuth: true
}
}
]
});
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && ! userAuthenticated()) {
next('/login');
} else {
next();
}
});
可理解为:需要认证,且认证通过。
一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some((record) => {
return record.meta.requiresAuth;
}
if (requiresAuth && ! userAuthenticated()) {
next('/login');
} else {
next();
}
});
若是使用子路由,且子路由都需要认证,则在父路由上添加requiresAuth,并使用$route.matched 来检查即可
不需要给所有需要控制的路由添加requiresAuth。使用to.matched只需要给较高一级的路由添加requiresAuth即可,其下的所有子路由不必添加。
vue-router在内部通过遍历路由数组的方式来挑选被显示的路由,并选取其中匹配到当前URL的第一个。
可以利用这个特点,来渲染一个显示错误页面:
const router = new VueRoute({
routes: [
// ...你的其他路由...
{
path: '*',
component: PageNotFound
}
]
});
当其他路由都匹配不到时,就会显示PageNotFound组件。
在使用嵌套路由时,如果没有匹配到子路由,则路由器会继续往下对其父路由之外的路由列表进行搜寻,所以通配符路由返回的都是同样的PageNotFound组件。如果想让子路由的错误页面也能在父组件中显示,则需要在子路由数组中添加该通配符路由:
const router = new VueRoute({
routes: [
{
path: '/settings',
component: PageSettings,
children: [
{
path: 'profile',
component: PageSettingsProfile
},
{
path: '*',
component: PageNotFound
}
]
},
{
path: '*',
component: PageNotFound
}
]
});
给路由器的路由加上一个name属性后,可用路由的名称来代替它们的路径:
const router = new VueRouter({
routes: [
{
path: '/',
name: 'home',
component: PageHome
},
{
path: '/user/:userId',
name: 'user',
component: PageUser
}
]
});
使用时,以下几种方式等同:
<router-link :to="{ name: 'home' }">Return to home</router-link>
<router-link :to="/home">Return to home</router-link>
<router-link :to="{ path: '/home' }">Return to home</router-link>
当然还可以用router.push()来跳转:
router.push({ name: 'home' });
这样当路径需要变动时,就不需要去单独修改使用到的地方了
指定参数可用params:
<router-link :to="{ name: 'user', params: { userId: 1234 }}">
User #1234
</router-link>
# 不是vue-router
vue add router
装好之后,在main.js中多出一个引入:import router from './router'
,并在根Vue实例新增属性 router
:
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
...
new Vue({
store,
router,
render: h => h(App)
}).$mount('#app')
这表明应用初始化时,router
和store(vuex)
会以属性的形式传到vue
实例中。
在src
根目录下会产生一个文件:router/index.js
:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
此文件作为router的配置文件存在,接下来具体分析这个文件。
当然也可以使用标签直接引入在线的 vue-router
配置文件router/index.js
从上往下依次是:
import
:Vue
、VueRouter
、页面模板文件Vue.use(VueRouter)
json
形式的路由表routes
:路径path
、名字name
、组建模板component
router
:模式mode
、基础路径base
、路由表routes
配置好之后就可以开始使用了,在需要以vue-router为规则显示页面的地方添加标签<router-view/>
。。。待续
使用过程中要特别注意区分 路由表routes,路由表对象route,路由对象router
拓展阅读: