PS:转载请注明出处
作者: TigerChain
地址https://www.jianshu.com/p/9a7d79249741
本文出自 TigerChain 简书 手把手教 Vue 系列
教程简介
- 1、阅读对象
本篇教程适合新手阅读,老手直接略过 - 2、教程难度
初级,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢 - 3、Demo 地址:https://github.com/tigerchain/vue-lesson 请看 06、Vue路由 这一节
- 4、演示地址
正文
一、什么是路由
以前在 React 的文章中说过路由这个东西,这里再说一下「再次加深一下记忆」。路由是什么我们可能不太理解,但是我说一个东西我们一定知道,就是"路由器",路由器的功能用一句话概括就是:数据从一个网络到另一个网络就是靠路由器来完成的[当然路由器的功能不仅仅于此]。
我们说的程序开发中的路由不是指路由器和网络协议中的路由,但是基本思想是一样的。而路由又分为前端路由和后端路由。
我们来看一个路由的简易图吧,有了这个图,大家对路由就有一个大致的了解了。
1、后端路由
举个栗子,分配一个站点,服务器地址是:http://192.168.1.200:8899
,在这个网站中提供了三个界面
http://192.168.1.200:8899/index.html 主页
http://192.168.1.200:8899/about/aboutus.html 关于我们页面
http://192.168.1.200:8899/feedback.html 反馈界面
当我们在浏览器输入 http://192.168.1.200:8899/index.html
来仿问界面的时候,web 服务器就会接收到这个请求,然后把 index.html 解析出来,并找到相应的 index.html 并展示出来,这就是路由的分发,路由的分发是通过路由功能来完成的
2、前端路由
虽然前端路由和后端路由的实现方式不一样,但是原理都有是相同的,在 H5 的 history Api 出来之前,前端路由的功能都是通过 hash 「散列值」 来实现的,hash 能兼容低版本的浏览器
PS:后端路由每次仿问一个页面都要向浏览器发送请求,然后服务端再响应解析,在这个过程中肯定会存在延迟,但是前端路由中仿问一个新的界面的时候只是浏览器的路径改变了,没有和服务端交互「所以不存在延迟」,这个对用户体验来说是大大的提高。如下所示
http://192.168.1.200:8080/#/index.html
http://192.168.1.200:8080/#/about/aboutus.html
http://192.168.1.200:8080/#/feedback.html
由于 web 服务器不会解析 # 后面的东西「所以通过 hash 能提高性能」,但是客户端的 js 可以拿到 # 后面的东西,有一个方法是 window.location.hash 来读取,使用这个方法来匹配到不同的方法上「配合前端的一些逻辑操作就完成路由功能,剩下只是关心接口调用」
3、举个栗子
假设有一个地址
http://www.xxx.com/path/a/b/c.html?key1=Tiger && key2=Chain && key3=fuck#/path/d/e.html
- 1、我们把这个地址分析一下「这个地址基本上包含了一个复杂地址的所有情况」
http:协议
www.xxx.com:域名
/path/a/b/c.html:路由,即服务器上的资源
?key1=Tiger && key2=Chain && key3=fuck:这个很好理解 Get 请求的参数
#/path/d/e.html:hash 也叫散列值,也叫锚点
上面的 hash 是和浏览器交互的,其它的都是和服务器进行交互
通过上述我们知道,前端路由的实现方式有两种:
(1)、一是改变 hash 值,监听 hashchange 事件,可以兼容低版本浏览器
(2)、二是通过 H5 的 history API 来监听 popState 事件,使用 pushState 和 replaceState 实现
- 2、hash 改变,不会导致浏览器刷新「请求服务器」,我们来写个 demo 验证一下
先看一下效果图
从图中我们可以看到,使用 hash 并不会导致浏览器刷新,并且我们 js 拿到了 hash 值并且打印出来了
- 3、源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>hash 实现前端路由</title>
<style>
#nav {
margin: 0;
border:0;
height: 40px;
border-top: #060 2px solid;
margin-top: 10px;
border-bottom: #060 2px solid;
background-color: red;
}
#nav ul {
margin: 0;
border: 0;
list-style: none;
line-height: 40px;
}
#nav li {
display: block;
float: left;
}
#nav a {
display: block;
color: #fff;
text-decoration: none;
padding: 0 20px;
}
#nav a:hover {
background-color: orange;
}
</style>
</head>
<body>
<h3>使用 hash 实现前端路由</h3>
<hr/>
<a href="#hash1">#hash1</a>
<a href="#hash2">#hash2</a>
<a href="#hash3">#hash3</a>
<a href="#hash4">#hash4</a>
<p/>
<div id = "show-hash-result" style="color:blue">
点击上面链接,并观察浏览器
</div>
<h4>定义一个简单的 tab 路由页面</h4>
<div id="nav">
<ul>
<li><a href="#/index.html">首页</a></li>
<li><a href="#/server">服务</a></li>
<li><a href="#/mine">我的</a></li>
</ul>
</div>
<div id="result"></div>
<script type="text/javascript">
window.addEventListener("hashchange", function(){
//变化后输出当前地址栏中的值
document.getElementById("show-hash-result").innerHTML = "当前的 hash 值是: "+location.hash;
//打印出当前 hash 值
console.log("当前的 hash 值是:"+window.location.hash) ;
});
</script>
<!-- 定义 router 的 js 代码块 -->
<script type="text/javascript">
//自定义一个路由规则
function CustomRouter(){
this.routes = {};
this.curUrl = '';
this.route = function(path, callback){
this.routes[path] = callback || function(){};
};
this.refresh = function(){
if(location.hash.length !=0){ // 如果 hash 存在
this.curUrl = location.hash.slice(1) || '/';
if(this.curUrl.indexOf('/')!=-1){ //这里粗略的把 hash 过滤掉
this.routes[this.curUrl]();
}
}
};
this.init = function(){
window.addEventListener('load', this.refresh.bind(this), false);
window.addEventListener('hashchange', this.refresh.bind(this), false);
}
}
//使用路由规则
var R = new CustomRouter();
R.init();
var res = document.getElementById('result');
R.route('/hash1',function () {
document.getElementById("show-hash-result").innerHTML = location.hash;
})
R.route('/index.html', function() {
res.style.height='150px';
res.style.width='300px';
res.style.background = 'green';
res.innerHTML = '<html>我是首页</html>';
});
R.route('/server', function() {
res.style.height='150px';
res.style.width='300px';
res.style.background = 'orange';
res.innerHTML = '我是服务页面';
});
R.route('/mine', function() {
res.style.background = 'red';
res.style.height='150px';
res.style.width='300px';
res.innerHTML = '我的界面';
});
</script>
</body>
</html>
以上代码只是为了演示前端路由的作用,一般情况下,这种路由我们是不需要自己写的,使用 react/vue 都会有相应的路由工具类,我们发现了 hash 只会改变浏览器地址,不会刷新浏览器
- H5 的 history
window 的 history 提供了对浏览器历史记录的访问功能,并且它暴露了一些方法和属性,让你在历史记录中自由的前进和后退,并且在 H5 中还可以操作历史记录中的数据。
我们在 chrome 浏览器的调试窗口中在 Console 中输入 window.history,会得到 history 的一些方法和属性,如下图所示
总结一下 history 的 API 如下:
interface History {
readonly attribute long length;
readonly attribute any state;
void go(optional long delta);
void back();
void forward();
//h5 引进以下两个方法
void pushState(any data, DOMString title, optional DOMString? url = null);
void replaceState(any data, DOMString title, optional DOMString? url = null);
};
- 1、
back():
在历史记录中后退
history.back() ;
- 2、
forward:
在历史记录中前进
history.forward();
- 3、
go():
移动到指定的历史记录点
history.go(-1)
其中正数是前进「+1就是前进一个界面」,负责是后退的意思「-1就是后退一个界面」
4、
length:
hisgory 的属性,显示 history 的长度5、
pushState(data,title[,url]):
给历史记录堆栈顶部添加一条记录
history.pushState(data,title[,url])
如果想更进一步的了解 H5 的 history ,推荐看这里:https://developer.mozilla.org/en-US/docs/Web/API/History_API,非常值得一看
从上面我们了解到,使用 H5 的 history 的 pushState 可以代替 hash,并且更加优雅,废话不多说,我们直接上效果图
从效果图中我们可以看到前端路由实现了,点击各个导航没有刷新浏览器,并且点击浏览器的回退按钮,会显示上一次记录,这都是使用 h5 history 的 pushState 和监听 onpopstate 实现的,这就是一个简单的 SPA ,基本上实现了和上面 hash 一样的功能
源码
我们只看核心代码
<h4>使用 h5 实现前端路由</h4>
<ul>
<li> <a onclick="home()">首页</a></li>
<li> <a onclick="message()">消息</a></li>
<li> <a onclick="mine()">我的</a></li>
</ul>
<div id="showContent" style="height:240px;width:200px;background-color:red">
home
</div>
<script type="text/javascript">
function home() {
// 添加到历史记录栈中
history.pushState({name:'home',id:1},null,"?page=home#index")
showCard('home')
};
function message() {
history.pushState({name:'message',id:2},null,"?page=message#haha")
showCard('message')
}
function mine(){
history.pushState({
id:3,
name:'mine'
},null,"?name=tigerchain&&sex=man")
showCard('mine')
}
// 监听浏览器回退 并且刷新到指定内容
window.addEventListener('popstate',function (event) {
var content = "";
if(event.state) {
content = event.state.name;
}
console.log(event.state)
console.log("history 中的历史栈中的 name :"+content)
showCard(content)
})
// 此方法和上面的方法是一毛一样的,只是两种不同的写法而已
// window.onpopstate = function (event) {
// var content = "";
// if(event.state) {
// content = event.state.name;
// }
// showCard(content);
// }
function showCard(name) {
console.log("当前的 hash 值是:"+location.hash)
document.getElementById("showContent").innerHTML = name;
}
</script>
以上就是通过 H5 的 history 实现的一个前端路由
我们稍微总结一下:
http://192.168.1.200:8080/index
http://192.168.1.200:8080/about/aboutus.html/#/flag=1
http://192.168.1.200:8080/feedback
- 后端路由:每次仿问都要向 server 发送一个请求,server 需要响应解析,会有延迟「网络不好更严重」
- 前端路由:只是改变浏览器的地址,不刷新浏览器,不和服务端交互,所以性能有大大的提高「用户体验提高」,并且前端路由有两种实现方式
(1)、实现 hash 并监听 hashchange 事件来实现
(2)、使用 H5 的 hisgory 的 pushState() 监听 popstate 方法来实现
到这里,我们大概对路由有一个整体的了解了,下面我们看看 veu 的路由
二、Vue 路由
Vue 中的路由,推荐使用官方支持的 vue-router 库,当然我们可以不使用 vue-router 可以使用三方的路由库,或者自己牛 b 完全可以自己写一个路由库「使用 hash 或 history」
本文中我们使用 vue-router 3.0.1来讲解,考虑到团队开发协作,我们先写一个使用 html 引入 vue.js 的方式来使用 vue-router,后面专注说使用模块化开发「使用 vue-cli 创建项目中使用 vue-router,这应该是团队开发的最佳方式」
html 中 引入 vue-router
1、废话不多了,直接写一个简单的 SPA 应用来感受一下
效果如下:
从上图中我们可以看到,我们使用 vue-router 实现了一个简单的类 hash 的路由功能
2、源码
- 1、引入 vue.js 和 vue-router.js
- 2、定义 Main、Message、Mine 组件
- 3、声明路由规则
声明路由规则,把路径所对应要跳转的组件先定义出来「相当于一个配置项」,到时候浏览器地址指定到对应的路由下会自动跳转到所对应的组件「这样就完成了路由功能」
- 4、创建 router 实例
创建 router 实例,并把 routes 传递进去
- 5、注册 router「把 router 注入到指定的挂载点下」
经过以上几步,路由功能就完成了,我们现在定义了路径,定义了路由所对应的组件,那组件要显示在哪里呢?那就前面 1 中的图中所显示的 <router-view />
中
可是如何让我在点击不同的按钮进不同的路由「不是的组件呢?」,这里就要使用到 <router-link to="路由配置中的路径">比如首页<router-link />
,从名字就可以看出来就是路由链接到那个路径,路径会匹配出相应的组件显示在 router-view
中
这样我们就完成了 vue-router 的基本使用,使用声明式导航 <router-link :to="...">
「其实就是创建了一个 a 标签」来完成了导航的链接
模块化「组件式开发」中 vue-router 的使用
如果使用 vue 开发手机 webapp ,那么页面跳转就非常多,路由使用的非常非常多,这样就更能体现出路由的强大之处,在这里我们使用 vue-cli 来创建一个 webapp ,来模拟一个简单的手机 app ,体验一下路由
效果如下:
- 1、初始化项目
使用 vue-cli 命令创建项目的时候,我们选择安装 vue-router,默认进去项目中就会有 vue-router 的配置,创建好的项目结构如下:
我们看到创建的目录结构多了一个 router 目录和 index.js 文件「vue-cli默认给添加的,如果你选了安装 vue-router 的话」
- 2、文件简单的分析
我们来看看 router 下的 index.js「路由配置文件」
我们可以看到默认 vue-cli 创建的带路由的项目把 router 配置文件单独的写在了 router/index.js 文件中了,并且我们看到默认指定打开的是 HelloWorld 组件「想配置路由组件,引入组件配置即可」
再看 main.js
一般来说 APP.vue 就是我们应用的首页,我们在 main.js 中注入路由,从而让整个应用都具有路由功能
- 3、再看 App.vue
核心就一个 <router-view/>
即可路由组件要显示的地方,默认路由路径是 / 对应的是 HelloWorld 组件,所以运行起应用就会显示 HelloWorld 组件,这里不显示运行后的结果了,我们使用 vue-cli 创建的 demo 样式看的太多了,自行运行查看即可
经过以上分析,我们基本上就把 vue-cli 带路由的 demo 说完了「核心就这几个东西,剩下的无非就是配置路由,然后组件组合,然后各种路由跳转」
- 4、修改 demo,一步步修改成效果图的样式
添加 First.vue 文件「核心代码
样式和数据「mockList 就是一个数组」就不截图了,完整例子可以查看 源码:https://github.com/tigerchain/vue-lesson/tree/master/06、Vue路由/vue-cli-router-webapp
其中的条目点击方法 nav(index)
就是路由跳转功能
以上点击方法我们使用编程式导航「跳转」,当然你也可以不传参数
this.$router.push({name:xxx,params:{xxxx}})
细心的朋友会发现,我们这个 name 叫 second 这是那里来的,它其实就是我们在 router/index.js 中配置的别名,再看 router/index.js 文件之前,我们先添加一个 Second.vue 组件
添加 Second.vue 组件「核心代码
这个没有什么好说的,就是一个有导航条并且接收到 First.vue 路由跳转传递过来的参数
修改 router/index.js 文件
前面我们使用跳转 this.$router.push({name:'second',params:{itemData:this.mockList[index]}})
中传了一个 name 为 second ,我们说了这是在 router/indes.js 中配置的,下面我们来看此文件
我们看到 / 对应的是 First 组件,/second 对应的是 Second 组件,并且分别给了 name 属性「在路由 push 的时候就可以使用到」,没什么好说的,当然你也可以不写 name 属性,路由跳转有几种写法
// 命名的路由
router.push({ path: '/second', params: { xxx:xxx }})
// 对象
router.push({ name: 'second', params: { xxx:xxx }})
等几种方式,如果有 name 属性直接使用即可,如果没有就使用 path「router/inde.js 中配置的路由 path」 即可,具体可以看官网:https://router.vuejs.org/zh-cn/essentials/navigation.html
如果想返回上一个界面,那么就使用 this.$router.go(-1)
和 h5 的 history 是一样的
修改 App.vue
我们来修改 App.vue 来让每个组件的内容都能全屏显示「我这里使用的 flexbox 布局,具体看源码」
这没有什么好说的,都是一些 css 样式的设置
运行查看效果
从效果图中我们可以看到我们已经实现了两个界面之间的跳转「通过路由功能」只不过这个界面跳转有点生硬,显的很不友好,下面我们给界面跳转添加一个动画
添加界面跳转动画
说到 vue 的跳转动画,我们就要说说 transition 过度效果,简单的说 transition 就是控制组件或标签的进入和离开的过度「这里不过多的介绍,我们看如何修改代码
修改 App.vue
- 第一步:给 router-view 添加过虑效果
- 第二步:给 transitionName 设置个属性值
在这里我们给 transitionName 设置一个值「这个值可以随便起一个名字,这里我就叫做 slide-right,向右滑动」
- 第三步:给 transitionName 添加进入,退出效果
这里我们给过度设置效果,一般使用 name-enter「过渡开始的状态」 name-enter-active「定义进入过渡的状态」、name-leave「离开过渡的开始状态」、name-leave-active「离开的过度状态」,其中 name 是可以的值是可以动态设置的,类如上面的 slide-right 和 slide-left,但是后面的部分是固定的,下面我们来使用 name
- 第四步:监听路由变化
我们先把路由定义一个统一的返回方法,打开 router/index.js,添加如下代码,使用 js 的 prototype「prototype 属性使您有能力向对象添加属性和方法」 属性即可
这样一来,router 就有了统一的返回方法,直接调用即可,我们修改返回按钮的返回方法如下:
this.$router.goBack()
- 第五步:监听路由变化
在这里我们使用到了 Vue 的 watch「这里不专门说,以后有需要会抽出来说」 方法,修改如下:
这样我们就可以监听是进入界面还是返回界面「这里 watch 和 data 方法是平级的,如果是返回的话,则从左边滑入到右边,打开新界面就是从右边滑到左边」
通过以上步骤,我们就给路由添加了一个过渡效果,我们来看看效果,就和刚开始的效果图是一样的
这样我就完成了组件式开发中的路由的基本功能,结束了吗?这里本应该可以结束了,但是我们前面学过组件,并且 vue 的核心特点之一就是组件化开发,我们这里使用的头部功能「两个界面头部如下」
我们可以看到两个界面的头部是何其的相似,这里我们完全可以把这个头部封装成一个组件呀,下面我们继续改造我们的代码
封装头部组件
- 1、新建一个 Head.vue 文件「核心代码」
- 2、在 First.vue 和 Second.vue 中引入
分三步:
第一步:引入组件
第二步:注册组件
第三步:使用组件
想在那个 vue 组件中使用以上三步就可以搞定了
到这里我们就把 head 组件封装完成了,并且达到使用的上的,具体可以看源代码
到此为止,我们把 vue-router 就介绍完了,总结一下
三、总结
- 路由的分类:前端路由和后端路由「区别和联系」
- 使用 hash 和 history 分别实现前端路由
- vue-router 的基本使用方法「使用 html 引入和模块化开发两种方式」
- vue-router 的举例子「手机端开发 webapp」
点赞富一生,转发富五代,更多文章请关注我的微信公号来查阅
公众号:TigerChain