一、vue-router是什么?
要搞明白这个问题,你必须先了解什么是单页应用(或叫单页Web应用),以下解释引自百度百科:
单页Web应用(single page web application,SPA),就是只有一张Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。
单页应用不存在页面跳转,它本身只有一个HTML页面。我们传统意义上的页面跳转在单页应用的概念下转变为了body
内某些元素的替换和更新,举个例子,假如我们有下面的页面index.html
:
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<login-page/>
</body>
</html>
这里现在加载的是登录组件。随后用户进行了登录,我们在登录请求的回调函数中把body
内的组件login-page
替换成了欢迎页组件welcome-page
,因此index.html
就变成了下面的样子:
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<welcome-page/>
</body>
</html>
显然index.html
仍然是它本身,但是由于用户操作,它的整个body的内容从登录组件变成了欢迎页组件,我们实现了传统意义上的页面跳转。但是要注意,由于当前加载的仍然是原来的HTML页面,因此浏览器不会完全重载,已下载的公共资源(如框架代码、普通的js代码等)不会被重新执行。浏览器只会下载与新的组件相关的js代码到本地执行,继而完成页面内容的更新。
这样,从视觉上,页面已经进行了跳转。但实际上,页面只是随着用户操作,实现了局部内容更新。所以你可以看到,相对于传统的多页面应用,单页应用有着极高的“跳转速度”,因此备受欢迎。
而vue-router
就是vue
框架下管理如何进行页面替换和更新的组件。借助vue-router
,你可以清晰地掌控整个页面内容更新的过程,拥有像浏览器地址栏一样强大的前进和回退功能。
router
的中文释义为“路由”,它是计算机网络中非常重要的概念,表示分组从源到目的地时,决定端到端路径的网络范围的进程。换句话说,就是分组数据包从源到目的地,经历了哪些网络节点。在单页应用中,它表示页面的更新过程中所经历的路径变化。由于没有页面跳转,因此路由无法直接映射到页面地址上,而是通过地址后面的hash值来体现的,举个例子,下面可能是登录页的地址:
https://xxxx/index.html#/login
#
后面的值我们称为哈希值,即/login
,它表示我们当前处在index.html
页面内的login
路径下。当用户执行了登录后,页面地址可能变化为:
https://xxxx/index.html#/welcome
我们看到,页面仍然是index.html
,但是它后面的哈希值却变成了/welcome
,这表示页面内部已经切换到了/welcome
所对应的组件。而我们的模板可能是这样写的:
<!DOCTYPE>
<html>
// 引入vue、vue-router以及登录、欢迎页等组件
<head>
...
</head>
<body>
<div id="app">
<router-view/>
</div>
<script>
const router = new VueRouter({ // 定义路由
routes: [
{ path: '/', redirect: '/login' }, //默认打开的页面
{ path: '/login', component: login-page },
{ path: '/welcome', component: welcome-page }
]
})
const app = new Vue({
router // 向vue实例导入路由
}).$mount('#app');
</script>
</body>
</html>
html我们指定当地址中的哈希值为/
时,重定向到/login
,而/login
和/welcome
则分别对应我们的登录组件login-page
和欢迎页组件welcome-page
。<router-view/>
是vue-router
使用的占位组件,它将根据哈希值动态被替换为对应的组件。
因此当你在地址栏输入https://xxxx/index.html
,会首先匹配到空路由/
,随后被重定向到/login
,vue-router
会将该路由对应的组件login-page
替换到占位组件<router-view/>
的位置。当用户执行了登录,并执行类似以下的操作时:
this.$axios.post('xxx', data).then(res => {
this.$router.push('/welcome');
})
该语句向路由堆栈中添加了新的值/welcome
,因此vue-router
会将对应的welcome-page
组件替换到占位组件<router-view/>
。从视觉上,登录页被替换成了欢迎页,而内部机制上,就是完成了一次组件替换,没有发生页面重载。
vue-router
的作用就是对单页应用中的上述过程进行管理,下面我们简单来看一下它的基本用法。
二、基本用法
关于vue-router
的基本语法,vue-router官网上有详尽的讲解,有需要的可以移步官网进行深入学习。这里我们进行一下大致的介绍。
1. 起步
要使用vue-router
,首先需要安装和引入它。
npm install vue-router --save // 安装
impoer VueRouter from 'vue-router'; // 引入
const router = new VueRouter({
routes: [
{ path: '/login', component: login-page },
{ path: '/welcome', component: welcome-page },
... // 为每个路由指定对应的组件
]
})
const app = new Vue({
router // 向vue实例导入路由
}).$mount('#app');
现在我们已经定义好了路由映射规则,并在vue实例中引入了它。当我们修改路由堆栈中的值时,vue-router
就会按照这套规则将抽象组件<router-view/>
替换为对应的组件。
此时的HTML模板可能像下面一样:
<body>
<div id="app">
<router-view/>
</div>
</body>
那我们怎么触发路由的变化呢?
当我们在创建vue实例并传入router实例对象时,vue会自动将这个router对象作为vue实例的$router
属性,即:
const app = new Vue({
router // 向vue实例导入路由
}).$mount('#app');
// app.$router => vue-router实例对象
变量app是一个vue实例,接下来我们便可以通过它的$router
属性去触发路由变化了。比如我们想修改路由,可以通过push
或replace
方法:
app.$router.push('/welcome');
// app.$router.replace('/welcome');
push方法会将前一个路由/login
保存在堆栈中,而replace则是直接替换上一个路由,因此在路由回退时两者的行为是不同的。
上面的语法称为编程式的导航,也就是通过js代码进行导航。除此之外,还可以通过路由组件的方式:
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<router-link to="/login">Go to login-page</router-link>
<router-link to="/welcome">Go to welcome-page</router-link>
</p>
这两个router-link
组件是由vue-router
定义的,它们在页面上会被渲染为一个a标签。每个组件带有一个to
属性,表示点击该组件需要跳转的路由地址,点击该组件的行为与执行push
方法的结果是一致的。这种方式常用于导航栏。
2. 动态路由
在实际应用中,我们可能需要将多个路由地址映射到同一个组件。比如我们现在有一个商品列表,点击某个商品就会进入该商品的详细信息页面。由于商品详细信息的格式是一致的,因此我们只编写了一个组件goods-detail
。
假设商品列表的路由地址为/goods
,而/goods/pants
,/goods/shoes
这样的路由地址都是某个商品对应的详细信息页面,它们使用的都是同一个组件goods-detail
。
这样的需求我们就需要用到动态路由了。此时我们可以像下面一样定义路由规则:
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/goods/:type', component: goods-deatil }
]
})
现在只要是符合/goods/xxx
这种模式的路由,都会被这个规则捕获,加载goods-detail
组件,并且路由中商品的类型被定义为了type属性。我们可以在goods-detail
组件中通过路由参数对象$route
获取这个值:
goods-detail
<template>
<div>
<p>{{ $route.params.type }}</p>
</div>
</template>
当路由值为/goods/shoes
时,组件内部会渲染成:
<div>
<p>shoes</p>
</div>
注意,$router
与$route
并不是同一个属性,前者是路由实例对象本身,而后者是该实例对象对应的参数对象,一般来说,操作路由应该使用$router
,如push、replace等,获取参数应该用$route
,如获取params值。
3. 嵌套路由
当多个路由地址的前缀路径相同时,嵌套路由可以减少路由规则定义的复杂性。比如,如果没有嵌套路由,我们可能会像下面一样定义一组路由:
const router = new VueRouter({
routes: [
{ path: '/goods/detail', component: goods-deatil },
{ path: '/goods/pay', component: goods-pay },
{ path: '/goods/feedback', component: goods-feedback }
]
})
使用嵌套路由,你可以写成下面的格式:
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/goods',
children: [
{ path: 'detail', component: goods-deatil },
{ path: 'pay', component: goods-pay },
{ path: 'feedback', component: goods-feedback }
]
]
})
children属性作为/goods
的子路由存在,它定义的路由会被拼接到上一级路由后面,即/goods/detail
,/goods/pay
,/goods/feedback
。
4. 命名路由
在定义路由规则时,你可以为路由指定一个名字,这样你就可以不用在每次切换路由时指定路由地址了,如:
const router = new VueRouter({
routes: [
{ path: '/goods/detail',
name: 'detail',
component: goods-deatil },
]
})
现在'/goods/detail'
这个路径已经被命名为detail
,因此你可以像下面一样跳转到这个路由:
<router-link :to="{name: 'detail', params: { id: '123'}}">detail</router-link>
// 或
this.$router.push({name: 'detail', params: { id: '123' }})
5. 命名视图
一个页面可以存在多个router-view
占位组件,那么当切换一个路由时,如何为每个router-view
指定要渲染的组件呢?答案就是使用命名视图,为每个router-view
指定一个名字,然后分别定义组件,如:
const router = new VueRouter({
routes: [
{ path: '/goods',
component: {
default: goods-content, // 默认视图
nav: goods-nav
} },
]
})
而页面模板可能像下面这样:
<div>
<router-view/>
<router-view name="nav"/>
</div>
当执行this.$router.push('/goods')
时,第一个router-view
会被替换为goods-content
组件,而第二个会被替换为goods-nav
组件。
6. 重定向和别名
重定向,顾名思义,就是将一个路由定向到另一个路由,我们在处理根路由时常常会用到。比如在最开始的例子中,我们只有/login
和/welcome
这两个组件,没有/
对应的组件。这样当用户在地址栏输入https://xxxx/index.html
或https://xxxx/index.html#/
时,页面会显示为白页。
假如我们希望让/
自动加载/login
的组件,可能需要下面的额外定义:
{ path: '/', component: login-page },
但我们想的是这时应用应该自动将路由直接置为/login
,于是我们会这样写:
{ path: '/', redirect: '/login' },
它表示路由地址/
现在将会被重定向到/login
,所以当用户输入https://xxxx/index.html
,地址栏的值会直接变成https://xxxx/index.html#/login
,这就是一次路由重定向。
别名则是给某个路由另起一个名字。如:
{
path: '/login',
component: login-page,
alias: '/userLogin'
}
现在输入https://xxxx/index.html#/login
和https://xxxx/index.html#/userLogin
都会匹配该路由。加载login-page
组件。
7. 路由传参
当我们在路由跳转时向路由组件传递了参数,除了可以像上面一样使用$route
对象来获取外,还可以通过props
属性,将参数转化为props。如:
{ path: '/user/:id', component: User, props: true },
props: true
意味着现在与路由相关的参数都被作为props
传入组件,因此你可以这样获取参数:
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
路由中的id参数将作为props中的id属性传入。
除了使用布尔值,props还可以使用对象或函数。
传入对象时,它会原样被设置为组件属性:
{ path: '/user/:id', component: User, props: { state: 'active' } }
组件内:
const User = {
props: ['state'],
template: '<div>User {{ state }}</div>'
}
传入函数时,可以基于路由参数对象,返回更复杂的参数:
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
URL/search?q=vue
会将{query: 'vue'}
作为属性传递给 SearchUser 组件。
8. History模式
vue-router
默认使用hash模式进行路由导航,因此在切换路由时,页面并不会跳转。但是有人可能觉得,在url中添加一个#
看起来并不美观,希望既可以不重载页面,又可以不在地址中出现丑陋的#
,HTML5的History接口为这种需求提供了可能。
这种模式的实现需要后端的配合,比如对于http://yoursite.com/user/id
这样的地址,我们希望它加载index.html
页面,就需要在后端配置好,否则就会返回404页面。
前端收到index.html页面后,自行根据url进行路由导航。想了解具体的实现,请参考MDN - History。
总结
本文只是关于vue-router
的基本语法介绍,还有一些进阶用法没有涉及,包括导航守卫、路由元信息、过渡动效、数据获取、滚动、路由懒加载等,需要进一步学习请参考vue-router官网。