vue.js插件使用(02) vue-router

概述

vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。

本文将以示例的形式来介绍vue-router的各个特性,一共包含6个示例,每个示例都有乞丐版,前5个示例有皇帝版。
乞丐版是将所有代码混杂在一起的HTML页面,皇帝版是基于vue-webpack-simple模板构建的。

乞丐版可以让你快速地了解到vue-router的一些特性和API;皇帝版则基于.vue组件和单独的路由配置,更适用于实际的应用。

本文的Demo和源代码已放到GitHub,如果您觉得内容不错,请点个赞,或在GitHub上加个星星!

第一个单页面应用 嵌套路由示例 具名路径示例 路由对象示例 让链接处于选中状态示例 钩子函数示例 GitHub Source

在GitHub上,乞丐版和皇帝版的目录结构如下:

├─06.Router		// vue-router示例目录	
│   ├─basic		// 乞丐版示例
│   ├──basic_01.html	// 第一个单页面应用
│   ├──basic_02.html	// 嵌套路由示例
│   ├──basic_03.html	// 具名路径示例
│   ├──basic_04.html	// 路由对象实例
│   ├──basic_05.html	// 让链接处于选中状态示例
│   ├──basic_06.html	// 钩子函数示例
│   ├─demo01		// 皇帝版,和basic_01.html对应
│   ├─demo02		// 皇帝版,和basic_02.html对应
│   ├─demo03		// 皇帝版,和basic_03.html对应
│   ├─demo04		// 皇帝版,和basic_04.html对应
│   ├─demo05		// 皇帝版,和basic_05.html对应

第一个单页面应用(01)

现在我们以一个简单的单页面应用开启vue-router之旅,这个单页面应用有两个路径:/home/about,与这两个路径对应的是两个组件Home和About。

image

1. 创建组件

首先引入vue.js和vue-router.js:

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

然后创建两个组件构造器Home和About:

var Home = Vue.extend({
    template: '<div><h1>Home</h1><p>{{msg}}</p></div>',
    data: function() {
        return {
            msg: 'Hello, vue router!'
        }
    }
})

var About = Vue.extend({
    template: '<div><h1>About</h1><p>This is the tutorial about vue-router.</p></div>'
})

2. 创建Router

var router = new VueRouter()

调用构造器VueRouter,创建一个路由器实例router。

3. 映射路由

router.map({
    '/home': { component: Home },
    '/about': { component: About }
})

调用router的map方法映射路由,每条路由以key-value的形式存在,key是路径,value是组件。
例如:'/home'是一条路由的key,它表示路径;{component: Home}则表示该条路由映射的组件。

4. 使用v-link指令

<div class="list-group">
    <a class="list-group-item" v-link="{ path: '/home'}">Home</a>
    <a class="list-group-item" v-link="{ path: '/about'}">About</a>
</div>

在a元素上使用v-link指令跳转到指定路径。

5. 使用<router-view>标签

<router-view></router-view>

在页面上使用<router-view></router-view>标签,它用于渲染匹配的组件。

6. 启动路由

var App = Vue.extend({})
router.start(App, '#app')

路由器的运行需要一个根组件,router.start(App, '#app') 表示router会创建一个App实例,并且挂载到#app元素。
注意:使用vue-router的应用,不需要显式地创建Vue实例,而是调用start方法将根组件挂载到某个元素。

7. 完整代码

 1 <!DOCTYPE html>
 2 <html>
 3 
 4     <head>
 5         <meta charset="UTF-8">
 6         <title>vue-router--第一个单页面应用(01)</title>
 7                     <link rel="stylesheet" href="../js/bootstrap.css">
 8             <script src="../js/vue.js"></script>
 9                     <script src="../js/vue-router.js"></script>
10     </head>
11 
12     <body>
13         <div id="app">
14             <div class="row">
15                 <div class="col-xs-offset-2 col-xs-8">
16                     <div class="page-header">
17                         <h2>Router Basic - 01</h2>
18                     </div>
19                 </div>
20             </div>
21             <div class="row">
22                 <div class="col-xs-2 col-xs-offset-2">
23                     <div class="list-group">
24                         <!--使用指令v-link进行导航-->
25                         <a class="list-group-item" v-link="{ path: '/home'}">Home</a>
26                         <a class="list-group-item" v-link="{ path: '/about'}">About</a>
27                     </div>
28                 </div>
29                 <div class="col-xs-6">
30                     <div class="panel">
31                         <div class="panel-body">
32                             <!--用于渲染匹配的组件-->
33                             <router-view></router-view>
34                         </div>
35                     </div>
36                 </div>
37             </div>
38         </div>
39         <template id="home">
40             <div>
41                 <h1>Home</h1>
42                 <p>{{msg}}</p>
43             </div>
44         </template>
45         <template id="about">
46             <div>
47                 <h1>About</h1>
48                 <p>This is the tutorial about vue-router.</p>
49             </div>
50         </template>
51     </body>
52     <script>
53         /* 创建组件构造器  */
54         var Home = Vue.extend({
55             template: '#home',
56             data: function() {
57                 return {
58                     msg: 'Hello, vue router!'
59                 }
60             }
61         })
62 
63         var About = Vue.extend({
64             template: '#about'
65         })
66 
67         /* 创建路由器  */
68         var router = new VueRouter()
69         
70         /* 创建路由映射  */
71         router.map({
72             '/home': {
73                 component: Home
74             },
75             '/about': {
76                 component: About
77             }
78         })
79         
80         router.redirect({
81             '/': '/home'
82         })
83 
84         /* 启动路由  */
85         var App = Vue.extend({})
86         router.start(App, '#app')
87     </script>
88 
89 </html>    
View Code

36

View Demo

当你从GitHub上获取到最新的源代码后,如果想运行皇帝版,以demo01为例,在Git Bash下执行以下命令:

npm run demo01-dev

image

然后在浏览器中访问地址http://127.0.0.1:8080

如果要编译和发布,请在Git Bash下执行以下命令:

npm run demo01-build

编写单页面的步骤

上面的6个步骤,可以说是创建一个单页面应用的基本步骤:

image

JavaScript

  1. 创建组件:创建单页面应用需要渲染的组件
  2. 创建路由:创建VueRouter实例
  3. 映射路由:调用VueRouter实例的map方法
  4. 启动路由:调用VueRouter实例的start方法

HTML

  1. 使用v-link指令
  2. 使用<router-view>标签

router.redirect

应用在首次运行时右侧是一片空白,应用通常都会有一个首页,例如:Home页。
使用router.redirect方法将根路径重定向到/home路径:

router.redirect({
    '/': '/home'
})

router.redirect方法用于为路由器定义全局的重定向规则,全局的重定向会在匹配当前路径之前执行。

执行过程

当用户点击v-link指令元素时,我们可以大致猜想一下这中间发生了什么事情:

  • vue-router首先会去查找v-link指令的路由映射
  • 然后根据路由映射找到匹配的组件
  • 最后将组件渲染到<router-view>标签

image

嵌套路由(02)

嵌套路由是个常见的需求,假设用户能够通过路径/home/news/home/message访问一些内容,一个路径映射一个组件,访问这两个路径也会分别渲染两个组件。

image

实现嵌套路由有两个要点:

  • 在组件内部使用<router-view>标签
  • 在路由器对象中给组件定义子路由

现在我们就动手实现这个需求。

组件模板:

<template id="home">
    <div>
        <h1>Home</h1>
        <p>{{msg}}</p>
    </div>
    <div>
        <ul class="nav nav-tabs">
            <li>
                <a v-link="{ path: '/home/news'}">News</a>
            </li>
            <li>
                <a v-link="{ path: '/home/message'}">Messages</a>
            </li>
        </ul>
        <router-view></router-view>
    </div>
</template>

<template id="news">
    <ul>
        <li>News 01</li>
        <li>News 02</li>
        <li>News 03</li>
    </ul>
</template>
<template id="message">
    <ul>
        <li>Message 01</li>
        <li>Message 02</li>
        <li>Message 03</li>
    </ul>
</template>

组件构造器:

var Home = Vue.extend({
    template: '#home',
    data: function() {
        return {
            msg: 'Hello, vue router!'
        }
    }
})

var News = Vue.extend({
    template: '#news'
})

var Message = Vue.extend({
    template: '#message'
})

路由映射:

router.map({
    '/home': {
        component: Home,
        // 定义子路由
        subRoutes: {
            '/news': {
                component: News
            },
            '/message': {
                component: Message
            }
        }
    },
    '/about': {
        component: About
    }
})

/home路由下定义了一个subRoutes选项,/news/message是两条子路由,它们分别表示路径/home/news/home/message,这两条路由分别映射组件News和Message。

完整代码

  1 <!DOCTYPE html>
  2 <html>
  3 
  4     <head>
  5         <meta charset="UTF-8">
  6         <title>vue-router嵌套路由(02)</title>
  7                     <link rel="stylesheet" href="../js/bootstrap.css">
  8             <script src="../js/vue.js"></script>
  9                     <script src="../js/vue-router.js"></script>
 10     </head>
 11 
 12     <body>
 13         <div id="app">
 14             <div class="row">
 15                 <div class="col-xs-offset-2 col-xs-8">
 16                     <div class="page-header">
 17                         <h2>Router Basic - 02</h2>
 18                     </div>
 19                 </div>
 20             </div>
 21             <div class="row">
 22                 <div class="col-xs-2 col-xs-offset-2">
 23                     <div class="list-group">
 24                         <a class="list-group-item" v-link="{ path: '/home'}">Home</a>
 25                         <a class="list-group-item" v-link="{ path: '/about'}">About</a>
 26                     </div>
 27                 </div>
 28                 <div class="col-xs-6">
 29                     <div class="panel">
 30                         <div class="panel-body">
 31                             <router-view></router-view>
 32                         </div>
 33                     </div>
 34                 </div>
 35             </div>
 36         </div>
 37 
 38         <template id="home">
 39             <div>
 40                 <h1>Home</h1>
 41                 <p>{{msg}}</p>
 42             </div>
 43             <div>
 44                 <ul class="nav nav-tabs">
 45                     <li>
 46                         <a v-link="{ path: '/home/news'}">News</a>
 47                     </li>
 48                     <li>
 49                         <a v-link="{ path: '/home/message'}">Messages</a>
 50                     </li>
 51                 </ul>
 52                 <router-view></router-view>
 53             </div>
 54         </template>
 55 
 56         <template id="news">
 57             <ul>
 58                 <li>News 01</li>
 59                 <li>News 02</li>
 60                 <li>News 03</li>
 61             </ul>
 62         </template>
 63         <template id="message">
 64             <ul>
 65                 <li>Message 01</li>
 66                 <li>Message 02</li>
 67                 <li>Message 03</li>
 68             </ul>
 69         </template>
 70 
 71         <template id="about">
 72             <div>
 73                 <h1>About</h1>
 74                 <p>This is the tutorial about vue-router.</p>
 75             </div>
 76         </template>
 77 
 78     </body>
 79     <script>
 80         var Home = Vue.extend({
 81             template: '#home',
 82             data: function() {
 83                 return {
 84                     msg: 'Hello, vue router!'
 85                 }
 86             }
 87         })
 88 
 89         var News = Vue.extend({
 90             template: '#news'
 91         })
 92 
 93         var Message = Vue.extend({
 94             template: '#message'
 95         })
 96 
 97         var About = Vue.extend({
 98             template: '#about'
 99         })
100 
101         var router = new VueRouter()
102         router.redirect({
103             '/': '/home'
104         })
105         router.map({
106             '/home': {
107                 component: Home,
108                 // 定义子路由
109                 subRoutes: {
110                     '/news': {
111                         component: News
112                     },
113                     '/message': {
114                         component: Message
115                     }
116                 }
117             },
118             '/about': {
119                 component: About
120             }
121         })
122 
123         var App = Vue.extend({})
124         router.start(App, '#app')
125     </script>
126 
127 </html>    
View Code

该示例运行如下:

37

View Demo

注意:这里有一个概念要区分一下,/home/news/home/message/home路由的子路由,与之对应的News和Message组件并不是Home的子组件。

具名路径(03)

在有些情况下,给一条路径加上一个名字能够让我们更方便地进行路径的跳转,尤其是在路径较长的时候。

我们再追加一个组件NewsDetail,该组件在访问/home/news/detail路径时被渲染,组件模板:

<template id="newsDetail">
    <div>
        News Detail - {{$route.params.id}} ......
    </div>
</template>

组件构造器:

var NewsDetail = Vue.extend({
    template: '#newsDetail'
})

具名路由映射

router.map({
    '/home': {
        component: Home,
        subRoutes: {
            '/news': {
                name: 'news',
                component: News,
                subRoutes: {
                    'detail/:id': {
                        name: 'detail',
                        component: NewsDetail
                    }
                }
            },
            '/message': {
                component: Message
            }
        }
    },
    '/about': {
        component: About
    }
})

注意:我们在定义/homes/news/home/news/detail/:id路由时,给该路由指定了name属性。
/:id是路由参数,例如:如果要查看id = '01'的News详情,那么访问路径是/home/news/detail/01。

Home组件和News组件模板:

<template id="home">
    <div>
        <h1>Home</h1>
        <p>{{msg}}</p>
    </div>
    <div>
        <ul class="nav nav-tabs">
            <li>
                <a v-link="{ name: 'news'}">News</a>
            </li>
            <li>
                <a v-link="{ path: '/home/message'}">Messages</a>
            </li>
        </ul>
        <router-view></router-view>
    </div>
</template>

<template id="news">
    <div>
        <ul>
            <li>
                <a v-link="{ name: 'detail', params: {id: '01'} }">News 01</a>
            </li>
            <li>
                <a v-link="{ path: '/home/news/detail/02'}">News 02</a>
            </li>
            <li>
                <a v-link="{ path: '/home/news/detail/03'}">News 03</a>
            </li>
        </ul>
        <div>
            <router-view></router-view>
        </div>
    </div>
</template>

<a v-link="{ name: 'news'}">News</a><a v-link="{ name: 'detail', params: {id: '01'} }">News 01</a>这两行HTML代码,使用了用了具名路径。

完整代码

  1 <!DOCTYPE html>
  2 <html lang="en">
  3 <head>
  4     <meta charset="UTF-8">
  5     <title>Vue.Router--具名路径(03)</title>
  6     <script src="../js/vue.js"></script>
  7     <script src="../js/vue-router.js"></script>
  8     <link rel="stylesheet" href="../js/bootstrap.css">
  9 </head>
 10 <body>
 11 //1.定义模板
 12 <template id="home">
 13     <div>
 14         <h1>Home</h1>
 15         <p>{{msg}}</p>
 16     </div>
 17     <div>
 18         <ul class="nav nav-tabs">
 19             <li>
 20                 <a v-link="{ path: '/home/news'}">News</a>
 21                 <!--<a v-link="{ name: 'news'}">News</a>-->
 22             </li>
 23             <li>
 24                 <a v-link="{ path: '/home/message'}">Messages</a>
 25             </li>
 26         </ul>
 27         <router-view></router-view>
 28     </div>
 29 </template>
 30 <template id="news">
 31     <div>
 32         <ul>
 33             <li>
 34                 <a v-link="{name:'detail',params:{id:'01'}}">News 01</a>
 35             </li>
 36             <li>
 37                 <a v-link="{path:'/home/news/detail/02'}">News 02</a>
 38             </li>
 39             <li>
 40                 <a v-link="{path:'/home/news/detail/03'}">News 03</a>
 41             </li>
 42             <div style="background-color: #5cb85c; margin:10px">
 43                 <router-view></router-view>
 44             </div>
 45         </ul>
 46     </div>
 47 </template>
 48 <template id="message">
 49     <ul>
 50         <li>Message 01</li>
 51         <li>Message 02</li>
 52         <li>Message 03</li>
 53     </ul>
 54 </template>
 55 <template id="about">
 56     <div><h1>About</h1><p>This is the tutorial about vue-router.</p></div>
 57 </template>
 58 <template id="newsDetail">
 59     <div>
 60         News Detail - {{$route.params.id}} ......
 61     </div>
 62 </template>
 63 <div id="app">
 64     <div class="row">
 65         <div class="col-xs-offset-2 col-xs-8">
 66             <div class="page-header">
 67                 <h2>Router Basic - 03</h2>
 68             </div>
 69         </div>
 70     </div>
 71     <div class="row">
 72         <div class="col-xs-2 col-xs-offset-2">
 73             <div class="list-group">
 74                 <a class="list-group-item" v-link="{ path: '/home'}">Home</a>
 75                 <a class="list-group-item" v-link="{ path: '/about'}">About</a>
 76             </div>
 77         </div>
 78         <div class="col-xs-6">
 79             <div class="panel">
 80                 <div class="panel-body">
 81                     <router-view></router-view>
 82                 </div>
 83             </div>
 84         </div>
 85     </div>
 86 </div>
 87 </div>
 88 </body>
 89 </html>
 90 <script>
 91 
 92     //1.2组件构造器
 93     var NewsDetail = Vue.extend({
 94         template: '#newsDetail'
 95     })
 96     var About = Vue.extend({
 97         template: '<div><h1>About</h1><p>This is the tutorial about vue-router.</p></div>'
 98     })
 99     var Home = Vue.extend({
100         template: '#home',
101         data: function() {
102             return {
103                 msg: 'Hello, vue router! '
104             }
105         }
106     })
107     var News = Vue.extend({
108         template: '#news'
109     })
110     var Message = Vue.extend({
111         template: '#message'
112     })
113     var About = Vue.extend({"template":'#about'})
114     //1.3创建Router
115     var router=new VueRouter();
116     //1.4路由映射
117     router.map({
118         '/home': {
119             component: Home,
120             //创建子路由
121             subRoutes: {
122                 '/news': {
123                     name: 'news',
124                     component: News,
125                     //创建子路由
126                     subRoutes: {
127                         'detail/:id': {
128                             name: 'detail',
129                             component: NewsDetail
130                         }
131                     }
132                 },
133                 '/message': {
134                     component: Message
135                 }
136             }
137         },
138         '/about': {
139             component: About
140         }
141     })
142     //1.5启动路由
143     var App=Vue.extend({});
144     router.start(App,'#app');
145     //默认加载home[需注释其他]
146     router.redirect({
147         '/': '/home'
148     })
149 </script>
View Code

该示例运行如下:

image

View Demo

v-link指令

用了这么久的v-link指令,是该介绍一下它了。

v-link 是一个用来让用户在 vue-router 应用的不同路径间跳转的指令。该指令接受一个 JavaScript 表达式,并会在用户点击元素时用该表达式的值去调用 router.go

具体来讲,v-link有三种用法:

<!-- 字面量路径 -->
<a v-link="'home'">Home</a>

<!-- 效果同上 -->
<a v-link="{ path: 'home' }">Home</a>

<!-- 具名路径 -->
<a v-link="{ name: 'detail', params: {id: '01'} }">Home</a>

v-link 会自动设置 <a> 的 href 属性,你无需使用href来处理浏览器的调整,原因如下:

  • 它在 HTML5 history 模式和 hash 模式下的工作方式相同,所以如果你决定改变模式,或者 IE9 浏览器退化为 hash 模式时,都不需要做任何改变。

  • 在 HTML5 history 模式下,v-link 会监听点击事件,防止浏览器尝试重新加载页面。

  • 在 HTML5 history 模式下使用 root 选项时,不需要在 v-link 的 URL 中包含 root 路径。

路由对象(04)

在使用了 vue-router 的应用中,路由对象会被注入每个组件中,赋值为 this.$route ,并且当路由切换时,路由对象会被更新。

路由对象暴露了以下属性:

  • $route.path 
    字符串,等于当前路由对象的路径,会被解析为绝对路径,如 "/home/news" 。
  • $route.params 
    对象,包含路由中的动态片段和全匹配片段的键值对
  • $route.query 
    对象,包含路由中查询参数的键值对。例如,对于 /home/news/detail/01?favorite=yes ,会得到$route.query.favorite == 'yes' 。
  • $route.router 
    路由规则所属的路由器(以及其所属的组件)。
  • $route.matched 
    数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。
  • $route.name 
    当前路径的名字,如果没有使用具名路径,则名字为空。

在页面上添加以下代码,可以显示这些路由对象的属性:

<div>
    <p>当前路径:{{$route.path}}</p>
    <p>当前参数:{{$route.params | json}}</p>
    <p>路由名称:{{$route.name}}</p>
    <p>路由查询参数:{{$route.query | json}}</p>
    <p>路由匹配项:{{$route.matched | json}}</p>
</div>

$route.path, $route.params, $route.name, $route.query这几个属性很容易理解,看示例就能知道它们代表的含义。

完整代码

  1 <!DOCTYPE html>
  2 <html lang="en">
  3 <head>
  4     <meta charset="UTF-8">
  5     <title>Vue.Router--路由对象(04)</title>
  6     <script src="../js/vue.js"></script>
  7     <script src="../js/vue-router.js"></script>
  8     <link rel="stylesheet" href="../js/bootstrap.css">
  9 </head>
 10 <body>
 11 <!--1.定义模板-->
 12 <template id="home">
 13     <div>
 14         <h1>Home</h1>
 15         <p>{{msg}}</p>
 16     </div>
 17     <div>
 18         <ul class="nav nav-tabs">
 19             <li>
 20                 <a v-link="{ path: '/home/news'}">News</a>
 21                 <!--<a v-link="{ name: 'news'}">News</a>-->
 22             </li>
 23             <li>
 24                 <a v-link="{ path: '/home/message'}">Messages</a>
 25             </li>
 26         </ul>
 27         <router-view></router-view>
 28     </div>
 29 </template>
 30 <template id="news">
 31     <div>
 32         <ul>
 33             <li>
 34                 <a v-link="{ path: '/home/news/detail/01?favorite=yes'}">News 01</a>
 35             </li>
 36             <li>
 37                 <a href="javascript:void(0)" @click="viewDetail">News 02</a>
 38             </li>
 39             <li>
 40                 <a v-link="{ path: '/home/news/detail/03'}">News 03</a>
 41             </li>
 42         </ul>
 43         <div style="background-color: #5cb85c; margin:10px">
 44             <router-view></router-view>
 45         </div>
 46     </div>
 47 </template>
 48 <template id="message">
 49     <ul>
 50         <li>Message 01</li>
 51         <li>Message 02</li>
 52         <li>Message 03</li>
 53     </ul>
 54 </template>
 55 <template id="about">
 56     <div><h1>About</h1><p>This is the tutorial about vue-router.</p></div>
 57 </template>
 58 <template id="newsDetail">
 59     <div>
 60         News Detail - {{$route.params.id}} ......
 61     </div>
 62 </template>
 63 <div id="app">
 64     <div class="row">
 65         <div class="col-xs-offset-2 col-xs-8">
 66             <div class="page-header">
 67                 <h2>Router Basic - 04</h2>
 68             </div>
 69         </div>
 70     </div>
 71     <div class="row">
 72         <div class="col-xs-2 col-xs-offset-2">
 73             <div class="list-group">
 74                 <a class="list-group-item" v-link="{ path: '/home'}">Home</a>
 75                 <a class="list-group-item" v-link="{ path: '/about'}">About</a>
 76             </div>
 77         </div>
 78         <div class="col-xs-6">
 79             <div class="panel">
 80                 <div class="panel-body">
 81                     <router-view></router-view>
 82                 </div>
 83             </div>
 84         </div>
 85         <div id="well">
 86             <div class="col-xs-offset-2 col-xs-8">
 87                 <div class="well">
 88                     <p>当前路径: <code>{{$route.path}}</code></p>
 89                     <p>当前参数: <code>{{$route.params | json}}</code></p>
 90                     <p>路由名称: <code>{{$route.name}}</code></p>
 91                     <p>路由查询参数: <code>{{$route.query | json}}</code></p>
 92                     <p>路由匹配项: <code>{{$route.matched | json}}</code></p>
 93                 </div>
 94             </div>
 95         </div>
 96     </div>
 97 </div>
 98 </div>
 99 </body>
100 </html>
101 <script>
102 
103     //1.2组件构造器
104     var NewsDetail = Vue.extend({
105         template: '#newsDetail'
106     })
107     var About = Vue.extend({
108         template: '<div><h1>About</h1><p>This is the tutorial about vue-router.</p></div>'
109     })
110     var Home = Vue.extend({
111         template: '#home',
112         data: function() {
113             return {
114                 msg: 'Hello, vue router! '
115             }
116         }
117     })
118     var News = Vue.extend({
119         template: '#news',
120         methods:{
121             viewDetail:function(){
122                 this.$route.router.go({
123                     name:'detail',
124                     params:{
125                         id:'02',
126                     }
127                 })
128             }
129         }
130     })
131     var Message = Vue.extend({
132         template: '#message'
133     })
134     var About = Vue.extend({"template":'#about'})
135     //1.3创建Router
136     var router=new VueRouter();
137     //1.4路由映射
138     router.map({
139         '/home': {
140             component: Home,
141             //创建子路由
142             subRoutes: {
143                 '/news': {
144                     name: 'news',
145                     component: News,
146                     //创建子路由
147                     subRoutes: {
148                         'detail/:id': {
149                             name: 'detail',
150                             component: NewsDetail
151                         }
152                     }
153                 },
154                 '/message': {
155                     component: Message
156                 }
157             }
158         },
159         '/about': {
160             component: About
161         }
162     })
163     //1.5启动路由
164     var App=Vue.extend({});
165     router.start(App,'#app');
166     //默认加载home[需注释其他]
167     router.redirect({
168         '/': '/home'
169     })
170 </script>
View Code

39

(由于$route.matched内容较长,所以没有将其显示在画面上)

这里我要稍微说一下$router.matched属性,它是一个包含性的匹配,它会将嵌套它的父路由都匹配出来。

例如,/home/news/detail/:id这条路径,它包含3条匹配的路由:

  1. /home/news/detail/:id
  2. /home/news
  3. /home

另外,带有 v-link 指令的元素,如果 v-link 对应的 URL 匹配当前的路径,该元素会被添加特定的class,该class的默认名称为v-link-active。例如,当我们访问/home/news/detail/03这个URL时,根据匹配规则,会有3个链接被添加v-link-active

image

View Demo

让链接处于活跃状态(05)

以上画面存在两个问题:

  1. 当用户点击Home链接或About链接后,链接没有显示为选中
  2. 当用户点击News或Message链接后,链接没有显示为选中

设置activeClass

第1个问题,可以通过设定v-link指令的activeClass解决。

<a class="list-group-item" v-link="{ path: '/home', activeClass: 'active'}">Home</a>
<a class="list-group-item" v-link="{ path: '/about', activeClass: 'active'}">About</a>
image

设定了v-link指令的activeClass属性后,默认的v-link-active被新的class取代。

image

第2个问题,为v-link指令设定activeClass是不起作用的,因为我们使用的是bootstrap的样式,需要设置a标签的父元素<li>才能让链接看起来处于选中状态,就像下面的代码所展现的:

<ul class="nav nav-tabs">
    <li class="active">
        <a v-link="{ path: '/home/news'}">News</a>
    </li>
    <li>
        <a v-link="{ path: '/home/message'}">Messages</a>
    </li>
</ul>

如何实现这个效果呢?你可能会想到,为Home组件的data选项追加一个currentPath属性,然后使用以下方式绑定class。

<ul class="nav nav-tabs">
    <li :class="currentPath == '/home/news' ? 'active': ''">
        <a v-link="{ path: '/home/news'}">News</a>
    </li>
    <li :class="currentPath == '/home/message' ? 'active': ''">
        <a v-link="{ path: '/home/message'}">Messages</a>
    </li>
</ul>

现在又出现了另一个问题,在什么情况下给currentPath赋值呢?

用户点击v-link的元素时,是路由的切换。
每个组件都有一个route选项,route选项有一系列钩子函数,在切换路由时会执行这些钩子函数。
其中一个钩子函数是data钩子函数,它用于加载和设置组件的数据。

var Home = Vue.extend({
    template: '#home',
    data: function() {
        return {
            msg: 'Hello, vue router!',
            currentPath: ''
        }
    },
    route: {
        data: function(transition){
            transition.next({
                currentPath: transition.to.path
            })
        }
    }
})

完整代码

  1 <!DOCTYPE html>
  2 <html>
  3 
  4 <head>
  5     <meta charset="UTF-8">
  6     <title></title>
  7     <link rel="stylesheet" href="../js/bootstrap.css">
  8     <script src="../js/vue.js"></script>
  9     <script src="../js/vue-router.js"></script>
 10 </head>
 11 
 12 <body>
 13 <div id="app">
 14     <div class="row">
 15         <div class="col-xs-offset-2 col-xs-8">
 16             <div class="page-header">
 17                 <h2>Router Basic - 05</h2>
 18             </div>
 19         </div>
 20     </div>
 21     <div class="row">
 22         <div class="col-xs-2 col-xs-offset-2">
 23             <div class="list-group">
 24                 <a class="list-group-item" v-link="{ path: '/home', activeClass: 'active'}">Home</a>
 25                 <a class="list-group-item" v-link="{ path: '/about', activeClass: 'active'}">About</a>
 26             </div>
 27         </div>
 28         <div class="col-xs-6">
 29             <div class="panel">
 30                 <div class="panel-body">
 31                     <router-view></router-view>
 32                 </div>
 33             </div>
 34         </div>
 35     </div>
 36 </div>
 37 
 38 <template id="home">
 39     <div>
 40         <h1>Home</h1>
 41         <p>{{msg}}</p>
 42     </div>
 43     <div>
 44         <ul class="nav nav-tabs">
 45             <li :class="currentPath == '/home/news' ? 'active': ''">
 46                 <a v-link="{ path: '/home/news'}">News</a>
 47             </li>
 48             <li :class="currentPath == '/home/message' ? 'active': ''">
 49                 <a v-link="{ path: '/home/message'}">Messages</a>
 50             </li>
 51         </ul>
 52         <router-view></router-view>
 53     </div>
 54 </template>
 55 
 56 <template id="about">
 57     <div>
 58         <h1>About</h1>
 59         <p>This is the tutorial about vue-router.</p>
 60     </div>
 61 </template>
 62 
 63 <template id="news">
 64     <div>
 65         <ul>
 66             <li>News 01</li>
 67             <li>News 02</li>
 68             <li>News 03</li>
 69         </ul>
 70     </div>
 71 
 72 </template>
 73 <template id="message">
 74     <ul>
 75         <li>Message 01</li>
 76         <li>Message 02</li>
 77         <li>Message 03</li>
 78     </ul>
 79 </template>
 80 </body>
 81 <script>
 82     var Home = Vue.extend({
 83         template: '#home',
 84         data: function() {
 85             return {
 86                 msg: 'Hello, vue router!',
 87                 currentPath: ''
 88             }
 89         },
 90         route: {
 91             data: function(transition){
 92                 transition.next({
 93                     currentPath: transition.to.path
 94                 })
 95             }
 96         }
 97     })
 98 
 99     var News = Vue.extend({
100         template: '#news'
101     })
102 
103     var Message = Vue.extend({
104         template: '#message'
105     })
106 
107     var About = Vue.extend({
108         template: '#about'
109     })
110 
111     var router = new VueRouter()
112     router.redirect({
113         '/': '/home'
114     })
115     router.map({
116         '/home': {
117             component: Home,
118             subRoutes: {
119                 '/news': {
120                     component: News
121                 },
122                 '/message': {
123                     component: Message
124                 }
125             }
126         },
127         '/about': {
128             component: About
129         }
130     })
131 
132     var App = Vue.extend({})
133     router.start(App, '#app')
134 </script>
135 
136 </html>
View Code

该示例运行效果如下:

image

View Demo

钩子函数(06)

路由的切换过程,本质上是执行一系列路由钩子函数,钩子函数总体上分为两大类:

  • 全局的钩子函数
  • 组件的钩子函数

全局的钩子函数定义在全局的路由对象中,组件的钩子函数则定义在组件的route选项中。

全局钩子函数

全局钩子函数有2个:

  • beforeEach:在路由切换开始时调用
  • afterEach:在每次路由切换成功进入激活阶段时被调用

组件的钩子函数

组件的钩子函数一共6个:

  • data:可以设置组件的data
  • activate:激活组件
  • deactivate:禁用组件
  • canActivate:组件是否可以被激活
  • canDeactivate:组件是否可以被禁用
  • canReuse:组件是否可以被重用

切换对象

每个切换钩子函数都会接受一个 transition 对象作为参数。这个切换对象包含以下函数和方法:

  • transition.to 
    表示将要切换到的路径的路由对象
  • transition.from 
    代表当前路径的路由对象。
  • transition.next() 
    调用此函数处理切换过程的下一步。
  • transition.abort([reason]) 
    调用此函数来终止或者拒绝此次切换。
  • transition.redirect(path) 
    取消当前切换并重定向到另一个路由。

钩子函数的执行顺序

全局钩子函数和组件钩子函数加起来一共8个,为了熟练vue router的使用,有必要了解这些钩子函数的执行顺序。

为了直观地了解这些钩子函数的执行顺序,在画面上追加一个Vue实例:

var well = new Vue({
    el: '.well',
    data: {
        msg: '',
        color: '#ff0000'
    },
    methods: {
        setColor: function(){
            this.color = '#' + parseInt(Math.random()*256).toString(16)
                        + parseInt(Math.random()*256).toString(16)
                        + parseInt(Math.random()*256).toString(16)
        },
        setColoredMessage: function(msg){
            this.msg +=  '<p style="color: ' + this.color + '">' + msg + '</p>'
        },
        setTitle: function(title){
            this.msg =  '<h2 style="color: #333">' + title + '</h2>'
        }
    }
})

well实例的HTML:

<div class="well">
    {{{ msg }}}
</div>

然后,添加一个RouteHelper函数,用于记录各个钩子函数的执行日志:

function RouteHelper(name) {
    var route = {
        canReuse: function(transition) {
            well.setColoredMessage('执行组件' + name + '的钩子函数:canReuse')
            return true
        },
        canActivate: function(transition) {
            well.setColoredMessage('执行组件' + name + '的钩子函数:canActivate')
            transition.next()
        },
        activate: function(transition) {
            well.setColoredMessage('执行组件' + name + '的钩子函数:activate')
            transition.next()
        },
        canDeactivate: function(transition) {
            well.setColoredMessage('执行组件' + name + '的钩子函数:canDeactivate')
            transition.next()
        },
        deactivate: function(transition) {
            well.setColoredMessage('执行组件' + name + '的钩子函数:deactivate')
            transition.next()
        },
        data: function(transition) {
            well.setColoredMessage('执行组件' + name + '的钩子函数:data')
            transition.next()
        }
    }
    return route;
}

最后,将这些钩子函数应用于各个组件:

var Home = Vue.extend({
    template: '#home',
    data: function() {
        return {
            msg: 'Hello, vue router!',
            path: ''
        }
    },
    route: RouteHelper('Home')
})

var News = Vue.extend({
    template: '#news',
    route: RouteHelper('News')
})

var Message = Vue.extend({
    template: '#message',
    route: RouteHelper('Message')
})

var About = Vue.extend({
    template: '#about',
    route: RouteHelper('About')
})

完整代码

  1 <!DOCTYPE html>
  2 <html>
  3 
  4 <head>
  5     <meta charset="UTF-8">
  6     <title></title>
  7     <link rel="stylesheet" href="../js/bootstrap.css">
  8     <script src="../js/vue.js"></script>
  9     <script src="../js/vue-router.js"></script>
 10 </head>
 11 
 12 <body>
 13 <div id="app">
 14     <div class="row">
 15         <div class="col-xs-offset-2 col-xs-8">
 16             <div class="page-header">
 17                 <h2>Router Basic - 06</h2>
 18             </div>
 19         </div>
 20     </div>
 21     <div class="row">
 22         <div class="col-xs-2 col-xs-offset-2">
 23             <div class="list-group">
 24                 <a class="list-group-item" v-link="{ path: '/home', activeClass: 'active'}">Home</a>
 25                 <a class="list-group-item" v-link="{ path: '/about', activeClass: 'active'}">About</a>
 26             </div>
 27         </div>
 28         <div class="col-xs-6">
 29             <div class="panel">
 30                 <div class="panel-body">
 31                     <router-view></router-view>
 32                 </div>
 33             </div>
 34         </div>
 35     </div>
 36 
 37     <div class="row">
 38         <div class="col-xs-offset-2 col-xs-8">
 39             <div class="well">
 40                 {{{ msg }}}
 41             </div>
 42         </div>
 43     </div>
 44 </div>
 45 
 46 <template id="home">
 47     <div>
 48         <h1>Home</h1>
 49         <p>{{msg}}</p>
 50     </div>
 51     <div>
 52         <ul class="nav nav-tabs">
 53             <li :class="path == '/home/news' ? 'active': ''">
 54                 <a v-link="{ path: '/home/news'}">News</a>
 55             </li>
 56             <li :class="path == '/home/message' ? 'active': ''">
 57                 <a v-link="{ path: '/home/message'}">Messages</a>
 58             </li>
 59         </ul>
 60         <router-view></router-view>
 61     </div>
 62 </template>
 63 
 64 <template id="about">
 65     <div>
 66         <h1>About</h1>
 67         <p>This is the tutorial about vue-router.</p>
 68     </div>
 69 </template>
 70 
 71 <template id="news">
 72     <div>
 73         <ul>
 74             <li>News 01</li>
 75             <li>News 02</li>
 76             <li>News 03</li>
 77         </ul>
 78     </div>
 79 </template>
 80 <template id="message">
 81     <ul>
 82         <li>Message 01</li>
 83         <li>Message 02</li>
 84         <li>Message 03</li>
 85     </ul>
 86 </template>
 87 </body>
 88 <script>
 89 
 90     var well = new Vue({
 91         el: '.well',
 92         data: {
 93             msg: '',
 94             color: '#ff0000'
 95         },
 96         methods: {
 97             setColor: function(){
 98                 this.color = '#' + parseInt(Math.random()*256).toString(16)
 99                         + parseInt(Math.random()*256).toString(16)
100                         + parseInt(Math.random()*256).toString(16)
101             },
102             setColoredMessage: function(msg){
103                 this.msg +=  '<p style="color: ' + this.color + '">' + msg + '</p>'
104             },
105             setTitle: function(title){
106                 this.msg =  '<h2 style="color: #333">' + title + '</h2>'
107             }
108         }
109     })
110 
111     function RouteHelper(name) {
112         var route = {
113             canReuse: function(transition) {
114                 well.setColoredMessage('执行组件' + name + '的钩子函数:canReuse')
115                 return true
116             },
117             canActivate: function(transition) {
118                 well.setColoredMessage('执行组件' + name + '的钩子函数:canActivate')
119                 transition.next()
120             },
121             activate: function(transition) {
122                 well.setColoredMessage('执行组件' + name + '的钩子函数:activate')
123                 transition.next()
124             },
125             canDeactivate: function(transition) {
126                 well.setColoredMessage('执行组件' + name + '的钩子函数:canDeactivate')
127                 transition.next()
128             },
129             deactivate: function(transition) {
130                 well.setColoredMessage('执行组件' + name + '的钩子函数:deactivate')
131                 transition.next()
132             },
133             data: function(transition) {
134                 well.setColoredMessage('执行组件' + name + '的钩子函数:data')
135                 transition.next()
136             }
137         }
138         return route;
139     }
140 
141     var Home = Vue.extend({
142         template: '#home',
143         data: function() {
144             return {
145                 msg: 'Hello, vue router!',
146                 path: ''
147             }
148         },
149         route: RouteHelper('Home')
150     })
151 
152     var News = Vue.extend({
153         template: '#news',
154         route: RouteHelper('News')
155     })
156 
157     var Message = Vue.extend({
158         template: '#message',
159         route: RouteHelper('Message')
160     })
161 
162     var About = Vue.extend({
163         template: '#about',
164         route: RouteHelper('About')
165     })
166 
167     var router = new VueRouter()
168     router.redirect({
169         '/': '/home'
170     })
171     router.map({
172         '/home': {
173             component: Home,
174             subRoutes: {
175                 '/news': {
176                     component: News
177                 },
178                 '/message': {
179                     component: Message
180                 }
181             }
182         },
183         '/about': {
184             component: About
185         }
186     })
187 
188     router.beforeEach(function(transition) {
189         well.setColor()
190         well.setTitle('跳转路径<span class="text-danger">[from = ' + transition.from.path + '], [to = ' + transition.to.path + ']</span>')
191         well.setColoredMessage('执行router的全局函数:beforeEach')
192         transition.next()
193     })
194 
195     router.afterEach(function(transition) {
196         well.setColoredMessage('执行router的全局函数:afterEach')
197     })
198 
199     var App = Vue.extend({})
200     router.start(App, '#app')
201 </script>
202 
203 </html>
View Code

 

我们按照以下步骤做个小实验:

  1. 运行应用(访问/home路径)
  2. 访问/home/news路径
  3. 访问/home/message路径
  4. 访问/about路径

 

 

image

View Demo

切换控制流水线

当用户点击了/home/news链接,然后再点击/home/message链接后,vue-router做了什么事情呢?它执行了一个切换管道

image

如何做到这些呢?这个过程包含一些我们必须要做的工作:

  1. 可以重用组件Home,因为重新渲染后,组件Home依然保持不变。

  2. 需要停用并移除组件News。

  3. 启用并激活组件Message。

  4. 在执行步骤2和3之前,需要确保切换效果有效——也就是说,为保证切换中涉及的所有组件都能按照期望的那样被停用/激活。

切换的各个阶段

我们可以把路由的切换分为三个阶段:可重用阶段,验证阶段和激活阶段。

我们以home/news切换到home/message为例来描述各个阶段。
(以下文字描述参考:http://router.vuejs.org/zh-cn/pipeline/index.html

1. 可重用阶段

检查当前的视图结构中是否存在可以重用的组件。这是通过对比两个新的组件树,找出共用的组件,然后检查它们的可重用性(通过 canReuse 选项)。默认情况下, 所有组件都是可重用的,除非是定制过。

image

2. 验证阶段

检查当前的组件是否能够停用以及新组件是否可以被激活。这是通过调用路由配置阶段的canDeactivate 和canActivate 钩子函数来判断的。

image

3.激活阶段

一旦所有的验证钩子函数都被调用而且没有终止切换,切换就可以认定是合法的。路由器则开始禁用当前组件并启用新组件。

image

此阶段对应钩子函数的调用顺序和验证阶段相同,其目的是在组件切换真正执行之前提供一个进行清理和准备的机会。界面的更新会等到所有受影响组件的 deactivate 和 activate 钩子函数执行之后才进行。

data 这个钩子函数会在 activate 之后被调用,或者当前组件组件可以重用时也会被调用。

总结

本文主要介绍了以下内容:

  • 介绍编写单页面应用的基本步骤
  • 介绍嵌套路由
  • 介绍具名路径
  • 介绍路由对象
  • 介绍钩子函数和执行顺序
  • 介绍组件切换控制流水线
posted @ 2016-10-26 14:30  银河系上的地球  阅读(1613)  评论(0编辑  收藏  举报