0%

HTML5 History 模式

vur-router 默认 hash 模式–使用 URL 的 hash 来模拟一个完整的 URL, 于是当 URL 改变时, 页面不会重新加载

如果不想要很丑的 hash, 我们可以用路由的 history 模式. 这种模式冲分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面

1
2
3
4
const router = new VueRouter({
mode: 'history',
routes: [...]
})

当你使用 history 模式时, URL 就像正常的 url. 不过 history 模式需要后台配置支持, 需要在后台配置如果 URL 匹配不到任何静态资源, 则应该返回同一个 index.html 页面, 这个页面就是你 app 依赖的页面

后端配置例子

Apache

1
2
3
4
5
6
7
8
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>

nginx

1
2
3
location / {
try_files $uri $uri/ /index.html;
}

Node.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const htpp = require('http')
const fs = require('fs')
const httpPort = 80
http
.createServer((req, res) => {
fs.readFile('index.html', 'utf-8', (err, content) => {
if (err) {
console.log('We cannot open "index.htm" file.')
}
res.writeHead(200, {
'Content-Type': 'text/html; charser=ytf-8'
})

red.end(content)
})
})
.listen(httpPort, () => {
console.log('server listening on: http://localhost:%s', httpPort)
})

基于 Node.js 的 Express,请考虑使用 connect-history-api-fallback 中间件

警告

这么做之后, 服务器就不再返回 404 错误页面, 因为对于所有路径都会返回 index.html 文件, 为了避免这种情况, 应该在 Vue 应用里面覆盖所有路由的情况下再给出一个 404 页面

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '*',
component: NotFoundComponent
}
]
})

或者, 如果使用的是 Node.js 服务器, 可以用服务端路由匹配到来的 URL, 并在没有匹配到路由的时候返回 404, 以实现回退

路由组件传参

在组件中使用 $route 会使之与其对应的路由形成高度耦合, 从而使组件只能在某些特定的 URL 上使用, 限制了其灵活性

使用 props 将组件和路由解耦

取代与 $route 的耦合

1
2
3
4
5
6
7
8
9
10
11
12
const User = {
template: `<div> User {{ $route.params.id }}</div>`
}

const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User
}
]
})

通过 props 解耦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}

const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User,
props: true
}
// 对于包含命名视图的路由, 必须分别为每个命名视图添加 'props' 选项
{
path: '/user/:id',
components:{
default: User,
sidebar: Sidebar,
props: {
default: true,
sidebar: false
}
}
}
]
})

布尔模式

如果 props 被设置为 true. route.params 将会被设置为组件属性

对象模式

如果 props 是一个对象, 它会被按原样设置为组件属性, 当 props 是静态的时候有用

1
2
3
4
5
6
7
8
9
10
11
const router = new VueRouter({
routes: [
{
path: '/promotion/from-newsletter',
component: Promotion,
Props: {
newslerrerPopup: false
}
}
]
})

函数模式

你可以创建一个函数返回 props. 这样你便可以将参数转换成另一种类型, 将静态值与基于路由的值结合等等.

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{
path: 'search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
})

URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件

请尽可能保持 props 函数为无状态的,因为它只会在路由发生变化时起作用。如果你需要状态来定义 props,请使用包装组件,这样 Vue 才可以对状态变化做出反应。

重定向

重定向也是通过 toutes 配置来完成的.

1
2
3
4
5
6
7
8
const router = new VueRouter({
routes: [
{
path: '/a',
redirect: '/b'
}
]
})

重定向的目标也可以是一个命名的路由:

1
2
3
4
5
6
7
8
9
10
const router = new VueRouter({
routes: [
{
path: '/a',
redirect: {
name: 'foo'
}
}
]
})

甚至可以是一个方法, 动态返回重定向目标

1
2
3
4
5
6
7
8
9
10
11
const router = new VueRouter({
routes: [
{
path: '/a',
redirect: to => {
// 方法接受 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}
}
]
})

注意 导航守卫 并没有应用在跳转路由上, 而仅仅应用在其目标上.

别名

/a 的别名是 /b, 意味着, 当用户访问 /b 时, URL 会保持为 /b, 但是路由匹配为 /a, 就像用户访问 /a 一样

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{
path: '/a',
component: A,
alias: '/b'
}
]
})

“别名”的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。

命名视图

有时候, 我们想要在一个页面内, 同时展示多个视图. 而不是嵌套展示, 可以使用命名视图.
如果 <router-view> 没有设置名字, name 默认为 default

1
2
3
<router-view class="one"></router-view>
<router-view class="two" name="a"></router-view>
<router-view class="shree" name="b"></router-view>

一个视图使用一个组件渲染, 因此对于同个路由, 多个视图就需要多个组件, 确保正确使用 components 配置

1
2
3
4
5
6
7
8
9
10
11
12
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})

嵌套命名视图

我们也有可能使用命名视图创建嵌套视图的复杂布局。这时你也需要命名用到的嵌套 router-view 组件

1
2
3
4
5
6
7
<!-- UserSettings.vue -->
<div>
<h1>User Settings</h1>
<NavBar />
<router-view />
<router-view name="helper" />
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
path: '/settings',
// 你也可以在顶级路由就配置命名视图
component: UserSettings,
children: [{
path: 'emails',
component: UserEmailsSubscriptions
}, {
path: 'profile',
components: {
default: UserProfile,
helper: UserProfilePreview
}
}]
}

命名路由

有时候, 通过一个名称来标识一个路由显得更方便一些, 特别是在链接一个路由, 或者是执行一些跳转的时候
可以在创建 Router 实例的时候, 在 routes 配置中给某个路由设置名称

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{
path: '/user/:userid',
name: 'user',
component: User
}
]
})

要链接到一个命名路由, 可以给 router-linkto 属性传一个对象

1
2
<!-- 声明式 -->
<router-link :to="{name: 'user', params: {userId: 123}}"> </router-link>
1
2
3
4
5
6
7
// 编程式
router.push({
name: 'user',
params: {
userId: 123
}
})

编程式导航

除了使用 <vue-router> 创建 a 标签来定义导航链接, 我们还可以借助 router 的实例方法, 通过编写代码来实现

router.push(location, onComplete?, onAbort)

在 Vue 实例内部, 可以通过 $router 访问路由实例, 因此可以调用 this.$router.push

想到导航到不同的 URL, 则使用 router.push 方法. 这个方法会想 history 栈添加一个新的记录, 所以当用户点击浏览器后退按钮时, 则回到之前的 URL

当点击 <router-linke> 时, 这个方法会在内部调用, 所以点击 <router-link> 等同于调用 router.push()

  • <router-link to=""> 声明式
  • router.replace 编程式

该方法的参数可以是一个字符串路径, 或者一个描述地址的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 字符串
router.push('foo')

// 对象
router.push({
path: 'home'
})

// 命名的路由
router.push({
name: 'user',
params: {
userId: 123
}
})

// 带查询参数
router.push({
path: 'register',
query: {
plan: 'private'
}
})

如果提供了 path,params 会被忽略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const userId = '123'

router.push({
// -> /user/123
name: 'user',
params: {
userId
}
})
router.push({
// -> /user/123
path: `/user/${userId}`
})

router.push({
// -> /user
path: '/user',
params: { userId }
})

如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1 -> /users/2),你需要使用 beforeRouteUpdate 来响应这个变化 (比如抓取用户信息)。

router.replace(location, onComplete, onAbort)

router.push 很像, 唯一的不同是, 他不会向 history 添加新纪录, 而是跟他的方法名一样, 替换掉当前的 history 记录

  • <router-link to="" replace> 声明式
  • router.replace 编程式

router.go(n)

这个方法的参数是一个整数, 意思是在 history 记录中向前或者后退多少步, 类似 window.history.go(n)

1
2
3
4
5
6
7
8
9
10
11
12
//  在浏览器记录中前进一步, 等同于 history.forward()
router.go(1)

// 后退一步记录 等同与 window.back()
router.go(-1)

// 前进 3 步记录
router.go(3)

// 如果 history 不够, 默认失败
router.go(100)
router.go(-100)

嵌套路由

实际生活中的应用界面, 通常由多层嵌套的组件组合而成. URL 中各段动态路径也按某种结构对应嵌套的各层组件
借助 vur-router 使用嵌套路由配置, 可以简单的表达这种关系

1
2
3
<div id="app">
<router-view></router-view>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const User = {
template: '<div>{{ $route.params.id }}</div>'
}

const routes = [
{
path: '/user',
component: User
}
]

const router = new VueRouter({
routes
})

这里的 <router-view> 是顶层的出口, 渲染最高级路由匹配到的组件.
一个被渲染的组件可以包含自己的嵌套 <router-view>

1
2
3
4
5
6
7
8
9
// 给 User 组件加上嵌套的 <router-view>
const User = {
template: `
<div>
<h2>User<h2>
<router-view></router-view>
</div>
`
}

要在嵌套的出口中渲染组件, 需要在 VurRouter 的参数中使用 children 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const router = new VueRouter({
routes: [
{
path: 'user',
component: User,
chidren: [
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
},
{
// 当 /user/:id/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
}
]
}
]
})

/ 开头的嵌套路径会被当做根路径,

当你访问 /user/foo 时,User 的出口是不会渲染任何东西,这是因为没有匹配到合适的子路由。如果你想要渲染点什么,可以提供一个 空的 子路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User,
children: [
// 当 /user/:id 匹配成功,
// UserHome 会被渲染在 User 的 <router-view> 中
{ path: '', component: UserHome }

// ...其他子路由
]
}
]
})

动态路由匹配

我们经常需要把某种模式匹配到的所有路由, 全部映射到同个组件.
如: 我们有个 User 组件, 对于所有 ID 各不相同的用户, 都要使用这个组件来渲染, 那么,我们可以在 vue-router 的路由路径中使用 动态路径参数来达到这个效果:

1
2
3
4
5
6
7
8
9
10
const User = {
template: '<div>User</div>'
}
const routed = [
// 动态路径参数以冒号开头
{
path: '/user/:id',
component: User
}
]

现在, 像 /user/foo/user/bar 都将映射到相同的路由

一个”路径参数”使用冒号:标记, 当匹配到一个路由时, 参数值会被设置到 this.$route.params. 可以在每个组件内使用.

1
2
3
4
// 输出用户id
const User = {
template: '<div>{{ this.$route.params.id }}</div'
}

可以在一个路由中设置多段”路径参数”, 对应的值都会设置到 $route.params

  • /user/:username 匹配: /user/ginger params: {username: "ginger"}
  • /user/:username/post/:postid 匹配: /user/ginger/post/123 params: {username: "ginger", postid: 123}

响应路由参数的变化

/user/foo导航到 /user/bar,原来的组件实例会被复用, 这意味着组件的生命周期钩子不会再被调用

复用组件事, 想对路由参数的变化做出响应的话, 可以简单的 watch $route 对象

1
2
3
4
5
6
7
8
const user = {
template: '...',
watch: {
'$route': (to, from) {
// 对路由变化做出响应
}
}
}

或者使用 beforeRouteUpdate 导航守卫

1
2
3
4
5
6
7
const User = {
template: '...'
beforeRouteUpdate(to , from. next) {
// react to route changes...
// don't forget to call next()
}
}

捕获所有路由或 404 Not found 路由

常规参数智慧匹配被 / 分隔的 URL 片段中的字符, 如果想匹配任意路径, 我们可以使用通配符 (*)

1
2
3
4
5
6
7
8
{
// 匹配所有路径
path: '*'
}
{
// 匹配user开头的所有路径
path: '/user-*'
}

当使用通配符时吗请确保路由的顺序是正确的, 也就是说含有通配符的路由应该放在最后, 路由(path: *)通常用于客户端 404 错误, 如果你使用了 History 模式, 需要配置服务器支持

当使用一个通配符时, $route.params 内会自动添加一个名为 pathMatch 参数, 它包含了 URl 通过通配符被匹配的部分

1
2
3
4
5
6
7
// 给出一个路由 ( path: 'user-*')
this.$route.push('user-admin')
this.$route.params.pathMatch // admin

// 给出一个路由 { path: '*' }
this.$route.push('/non-existing')
this.$route.params.pathMatch // '/non-existing'

高级匹配模式

vue-router 使用 path-to-regexp 作为路径匹配引擎, 所以支持很多高级的匹配模式

匹配优先级

路由匹配优先级按照路由的定义顺序, 谁先定义的, 谁的优先即就最高

起步

用 Vue.js + Vue Router 创建单页面应用, 通过 Vue 组合组件来组成应用程序, 将 Vue Router 添加进来. 将组件映射到路由, 然后告诉 Vue Router 在哪里渲染他它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
<h1>Hi Ginger</h1>
<p>
<!-- 使用 router-link 组件来导航 -->
<!-- 通过传入 to 属性指定链接 -->
<!-- router-link 默认会被渲染成一个 a 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到组件将渲染在这里 -->
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>Foo</div>' }
const Bar = { template: '<div>Bar</div>' }

// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
const routes = [
{
path: '/foo',
component: Foo
},
{
path: '/bar',
component: Bar
}
]

// 3. 创建 router 实例, 然后传 routes 配置
const router = new VueRouter({
routes
})

// 4. 创建和挂载根实例
const app = new Vue({
router
}).$mount('#app')

通过注入路由器, 我们可以在任何组件内通过 this.$router 访问路由器, 也可以通过 this.$route 访问当前路由

1
2
3
4
5
6
7
8
9
10
11
12
13
// Home.vue
export default {
computed: {
username() {
return this.$route.params.username
}
},
methods: {
goBack() {
window.history.length > 1 ? this.router.go(-1) : this.$router.push('/')
}
}
}

<router-link> 对应的路由匹配成功,将自动设置 class 属性值 .router-link-active。

介绍

Vue Router 是 Vue.js 官方的路由管理器. 他和 Vue.js 的核心深度集成. 让构建单页面应用变得易如反掌. 包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的试图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式, 在 IE9 中自动降级
  • 自定义的滚动条行为

安装

直接下载/CDN

https://unpkg.com/vue-router/dist/vue-router.js

在 Vue 后面加载 vur-router, 他会自动安装:

1
2
<script src="vue.js"></script>
<script src="vue-router.js"></script>

NPM

1
npm install vue-router

如果在一个模块化工程中使用它, 必须要通过Vue.use()地安装路由功能:

1
2
3
4
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

构建开发板

1
2
3
4
git clone https://github.com/vuejs/vue-router.git node_modules/vue-router
cd node_modules/vue-router
npm install
npm run build