Vue-Router
History 模式
History 的路由模式,依赖了一个关键的属性window.history。
window.history
是一个只读属性,用来获取 History 对象的引用。History 对象提供了操作浏览器会话历史(浏览器地址栏中访问的页面,以及当前页面中通过框架加载的页面)的接口,使用window.history
我们可以实现以下与路由相关的重要能力:
(1) 在 history 中跳转。
使用window.history.back()
、window.history.forward()
和window.history.go()
方法来完成在用户历史记录中向后和向前的跳转。
(2) 添加和修改历史记录中的条目。
HTML5 引入了history.pushState()
和history.replaceState()
方法,它们分别可以添加和修改历史记录条目。这两个 API 都会操作浏览器的历史栈,而不会引起页面的刷新。区别在于,pushState()
会增加一条新的历史记录,而replaceState()
则会替换当前的历史记录:
Hash 模式
History 模式需要依赖 HTML5 History API(IE10 以上),以及服务器的配置来支持,所以也有不少的开发者会使用 Hash 模式来管理 Web 应用的路由。而 Hash 模式主要依赖 Location 对象的 hash 属性(location.hash
)和hashchange
事件,我们来分别看一下。
(1) Location 对象
window.location
用来获取 Location 对象的引用。Location 对象存储在 Window 对象的 Location 属性中,表示当前 Web 地址。Location 对象提供的属性如表 7-1:
表 7-1 Location 属性
Location 属性 | 描述 | 示例,https://www.test.com/en-US/search?q=URL#search-results |
---|---|---|
hash | 设置或返回从井号(#)开始的 URL(锚) | #search-results |
host | 设置或返回主机名和当前 URL 的端口号 | www.test.com |
hostname | 设置或返回当前 URL 的主机名 | www.test.com |
href | 设置或返回完整的 URL | https://www.test.com/en-US/search?q=URL#search-results |
pathname | 设置或返回当前 URL 的路径部分 | /en-US/search |
port | 设置或返回当前 URL 的端口号 | 默认 80 端口 |
protocol | 设置或返回当前 URL 的协议 | https: |
search | 设置或返回从问号(?)开始的 URL(查询部分) | ?q=URL |
location.hash
的设置和获取,并不会造成页面重新加载,利用这一点,我们可以记录页面关键信息的同时,提升页面体验。
(2) hashchange 事件。
当一个窗口的 hash 改变时就会触发hashchange
事件。hashchange
事件对象有两个属性,newURL
为当前页面新的 URL,oldURL
为当前页面旧的 URL。
Vue Router 实现
前面介绍了 History 模式和 Hash 模式两种,我们常见的路由库也都同样会提供这两种方式,Vue Router 也不例外。
路由模式 | 说明 | 示例 |
---|---|---|
hash | 使用 URL hash 值来作路由(支持所有浏览器,包括不支持 HTML5 History Api 的浏览器) | a.com/#/pageone |
history | 充分利用history.pushState API 来完成 URL 跳转而无须重新加载页面 | a.com/pageone |
需要注意的地方是,History 模式需要依赖 HTML5 History API(IE10 以上)和服务器配置。你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个index.html
页面,这个页面就是你 app 依赖的页面
安装和使用
1. 安装依赖
我们通过 npm 来安装 Vue Router(当然也可以使用 yarn):
# 安装依赖
npm install vue-router
2. 引入 vue-router
在 Vue 中,我们可以通过插件的方式来使用一些工具库、组件系统等。我们需要通过全局方法Vue.use()
使用插件(要在调用new Vue()
启动应用之前完成)。Vue Router 同样通过插件的方式提供,所以我们可以这样来使用:
// main.js
import Vue from "vue";
// 引入 vue-router
import VueRouter from "vue-router";
Vue.use(VueRouter); // 使用 vue-router
// 后面是 new Vue() 相关逻辑
路由设计与配置
// main.js
// 前面是使用 Vue Router 插件的部分,省略
// 配置路由信息
const routes = [
// 以 / 开头的嵌套路径会被当作根路径
{
path: "/home",
component: Home,
name: "Home",
children: [
{ path: "page1", component: Page1, name: "Page1" },
{ path: "page2", component: Page2, name: "Page2" }
]
},
// 通配符 * 会匹配所有路径
// 路由 { path: '*' } 通常用于客户端 404 错误
// 含有通配符的路由应该放在最后
{ path: "*", redirect: { name: "Home" } }
];
const router = new VueRouter({
routes // (缩写)相当于 routes: routes
});
// 启动一个 Vue 应用
new Vue({
el: "#app",
router, // 传入路由能力
render: h => h(App)
});
我们需要在 App 组件中,使用<router-view>
添加最顶层的出口,渲染最高级路由匹配到的组件:
同时,我们需要在 Home 页面中使用<router-view>
来展示子路由界面:
<!-- App.vue -->
<template>
<!-- 使用 <router-view></router-view> 来渲染最高级路由匹配到的组件 -->
<router-view></router-view>
</template>
<!-- Home.vue -->
<template>
<div>
<div>Home</div>
<div>
<router-link :to="{ name: 'Page1'}" tag="button">goto Page1</router-link>
<router-link :to="{ name: 'Page2'}" tag="button">goto Page2</router-link>
</div>
<!-- 子路由界面 -->
<!-- 如果路由为 /home/page1,此处是 Page1 组件 -->
<!-- 如果路由为 /home/page2,此处是 Page2 组件 -->
<router-view></router-view>
</div>
</template>
<router-view>
其实可以理解为占位符,而占位的内容是匹配到的路由信息对应的组件,这个组件会替代<router-view>
进行渲染。
导航方式
在 Vue Router 中,有两种导航方式:
(1) router
编程式导航。
(2) <router-link>
声明式导航。
编程式导航
Vue Router 提供了router
的实例方法,通过编写代码来实现导航功能。在 Vue 实例内部,你可以通过$router
访问路由实例,我们来看看该实例提供的一些方法。
router.push
想要导航到不同的 URL,则使用router.push
方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。使用方式如下:
// 字符串
router.push("/home");
// 对象
router.push({ path: "/home" });
// 命名的路由
router.push({ name: "Home" });
router.replace
跟router.push
很像,唯一的不同就是,它不会向 history 添加新记录,而会替换掉当前的 history 记录。
router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前(正整数)或者后退(负整数)多少步,类似window.history.go(n)
:
声明式导航
<router-link>
组件支持用户在具有路由功能的应用中 (点击) 导航。通过to
属性指定目标地址,默认渲染成带有正确链接的<a>
标签,可以通过配置tag
属性生成别的标签。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。
<router-link>
Props 说明
<router-link> Props | 说明 | 类型 |
---|---|---|
to | 表示目标路由的链接。当被点击后,内部会立刻把to 的值传到router.push() ,所以这个值可以是一个字符串或者是描述目标位置的对象 | string /Location |
replace | 当点击时,会调用router.replace() 而不是router.push() ,于是导航后不会留下 history 记录 | boolean (默认false ) |
tag | 指定<router-link> 渲染成某种标签。同样它还是会监听点击,触发导航 | string (默认"a" ) |
active-class | 设置链接激活时使用的 CSS 类名 | string (默认"router-link-active" ) |
event | 声明可以用来触发导航的事件 | string /Array<string> (默认"click" ) |
Vue Router中 exact-active-class与active-class使用场景有什么区别?
router-link
默认情况下的路由是模糊匹配,例如当前路径是 /article/1
那么也会激活 <router-link to="/article">
,所以当设置 exact-active-class
以后,这个 router-link
只有在当前路由被全包含匹配时才会被激活 exact-active-class
中的class。
例如
<router-link to="/article" active-class="router-active"></router-link>
当用户访问 /article/1
时会被激活为
<a href="#/article" class="router-active"></a>
而当使用
<router-link to="/article" exact-active-class="router-active"></router-link>
当用户访问 /article/1
时,不会激活这个link的class
<a href="#/article"></a>
路由传参
有些时候,我们需要在 URL 上带一些参数来标识当前内容。因为页面逻辑是通用的,只有内容不一样,这些内容的数据常常是根据某个 ID 来从后台获取,例如根据 ID 从后台获取某本书的详细信息。如果我们希望刷新页面的时候该 ID 不会丢失,则需要把这个标识带到我们的 URL 里。通常我们有两种方式来携带:
(1) /page/detail?id=123
,在 Vue Router 中用query
表示。
(2) /page/detail/123
,在 Vue Router 中用params
表示。
params 传参
params 的传参模式,我们需要首先在路由配置中进行特殊的配置(冒号":"标记)
// 动态路径参数以冒号 ":" 开头
{ path: "page1/:id", component: Page1, name: "Page1" },
{ path: "page2", component: Page2, name: "Page2" }
我们在跳转的时候,可以通过编程式导航或声明式导航来传参:
// 编程式导航传参
this.$router.push({ name: "Page1", params: { id: 123 } });
// 会跳转到 /home/page1/123
<!-- 声明式导航传参 -->
<router-link :to="{ name: 'Page1', params: {id: 1234}}">goto Page1</router-link>
<!-- 点击会跳转到 /home/page1/1234 -->
当匹配到一个路由时,参数值会被设置到$route.params
,可以在每个组件内使用:this.$route.params.id
query 传参
params 传参有一个不方便的地方,即我们必须要传入一个动态路径参数才能匹配到对应的页面。
query 的传参模式,我们不需要像 params 传参一样使用/xxxx/:xxx
这样更改路由配置,只需要在导航的时候传参:
// 编程式导航传参
this.$router.push({ name: "Page2", query: { id: 123 } });
// 会跳转到 /home/page2?id=123
<!-- 声明式导航传参 -->
<router-link :to="{ name: 'Page2', query: {id: 1234}}">goto Page2</router-link>
<!-- 点击会跳转到 /home/page2?id=1234 -->
在使用这样的方式导航之后,参数值会被设置到$route.query
,同样支持在每个组件内使用:
$route.query.id