07、手把手教Vue--路由「Vue-router」

96
TigerChain
2018.09.27 07:42* 字数 4324

PS:转载请注明出处
作者: TigerChain
地址https://www.jianshu.com/p/9a7d79249741
本文出自 TigerChain 简书 手把手教 Vue 系列

大纲

教程简介

  • 1、阅读对象
    本篇教程适合新手阅读,老手直接略过
  • 2、教程难度
    初级,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢
  • 3、Demo 地址:https://github.com/githubchen001/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-router

从图中我们可以看到,使用 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

总结一下 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,并且更加优雅,废话不多说,我们直接上效果图

history-demo

从效果图中我们可以看到前端路由实现了,点击各个导航没有刷新浏览器,并且点击浏览器的回退按钮,会显示上一次记录,这都是使用 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-in-html-router

从上图中我们可以看到,我们使用 vue-router 实现了一个简单的类 hash 的路由功能

2、源码

  • 1、引入 vue.js 和 vue-router.js
import-aboutjs
  • 2、定义 Main、Message、Mine 组件
custom-vue-component
  • 3、声明路由规则
decale-router

声明路由规则,把路径所对应要跳转的组件先定义出来「相当于一个配置项」,到时候浏览器地址指定到对应的路由下会自动跳转到所对应的组件「这样就完成了路由功能」

  • 4、创建 router 实例
create-router-instance

创建 router 实例,并把 routes 传递进去

  • 5、注册 router「把 router 注入到指定的挂载点下」
register-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 ,体验一下路由

效果如下

router-demo
  • 1、初始化项目
vue-init-router-demo

使用 vue-cli 命令创建项目的时候,我们选择安装 vue-router,默认进去项目中就会有 vue-router 的配置,创建好的项目结构如下:

folder-struct

我们看到创建的目录结构多了一个 router 目录和 index.js 文件「vue-cli默认给添加的,如果你选了安装 vue-router 的话」

  • 2、文件简单的分析

我们来看看 router 下的 index.js「路由配置文件」

router-indexjs

我们可以看到默认 vue-cli 创建的带路由的项目把 router 配置文件单独的写在了 router/index.js 文件中了,并且我们看到默认指定打开的是 HelloWorld 组件「想配置路由组件,引入组件配置即可」

再看 main.js

router-mainjs

一般来说 APP.vue 就是我们应用的首页,我们在 main.js 中注入路由,从而让整个应用都具有路由功能

  • 3、再看 App.vue

核心就一个 <router-view/> 即可路由组件要显示的地方,默认路由路径是 / 对应的是 HelloWorld 组件,所以运行起应用就会显示 HelloWorld 组件,这里不显示运行后的结果了,我们使用 vue-cli 创建的 demo 样式看的太多了,自行运行查看即可

经过以上分析,我们基本上就把 vue-cli 带路由的 demo 说完了「核心就这几个东西,剩下的无非就是配置路由,然后组件组合,然后各种路由跳转」

  • 4、修改 demo,一步步修改成效果图的样式

添加 First.vue 文件「核心代码

add-firstvue

样式和数据「mockList 就是一个数组」就不截图了,完整例子可以查看 源码:https://github.com/githubchen001/vue-lesson/tree/master/06、Vue路由/vue-cli-router-webapp

其中的条目点击方法 nav(index) 就是路由跳转功能

first-item-nav-method

以上点击方法我们使用编程式导航「跳转」,当然你也可以不传参数

  this.$router.push({name:xxx,params:{xxxx}})

细心的朋友会发现,我们这个 name 叫 second 这是那里来的,它其实就是我们在 router/index.js 中配置的别名,再看 router/index.js 文件之前,我们先添加一个 Second.vue 组件

添加 Second.vue 组件「核心代码

second-vue

这个没有什么好说的,就是一个有导航条并且接收到 First.vue 路由跳转传递过来的参数

修改 router/index.js 文件

前面我们使用跳转 this.$router.push({name:'second',params:{itemData:this.mockList[index]}}) 中传了一个 name 为 second ,我们说了这是在 router/indes.js 中配置的,下面我们来看此文件

modify-router-indexjs

我们看到 / 对应的是 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 布局,具体看源码」

modify-appvue

这没有什么好说的,都是一些 css 样式的设置

运行查看效果

vue-cli-router-one

从效果图中我们可以看到我们已经实现了两个界面之间的跳转「通过路由功能」只不过这个界面跳转有点生硬,显的很不友好,下面我们给界面跳转添加一个动画

添加界面跳转动画

说到 vue 的跳转动画,我们就要说说 transition 过度效果,简单的说 transition 就是控制组件或标签的进入和离开的过度「这里不过多的介绍,我们看如何修改代码

修改 App.vue

  • 第一步:给 router-view 添加过虑效果
add-transition
  • 第二步:给 transitionName 设置个属性值
transition-data

在这里我们给 transitionName 设置一个值「这个值可以随便起一个名字,这里我就叫做 slide-right,向右滑动」

  • 第三步:给 transitionName 添加进入,退出效果
transition-setcss

这里我们给过度设置效果,一般使用 name-enter「过渡开始的状态」 name-enter-active「定义进入过渡的状态」、name-leave「离开过渡的开始状态」、name-leave-active「离开的过度状态」,其中 name 是可以的值是可以动态设置的,类如上面的 slide-right 和 slide-left,但是后面的部分是固定的,下面我们来使用 name

  • 第四步:监听路由变化

我们先把路由定义一个统一的返回方法,打开 router/index.js,添加如下代码,使用 js 的 prototype「prototype 属性使您有能力向对象添加属性和方法」 属性即可

addGoBackToRouter

这样一来,router 就有了统一的返回方法,直接调用即可,我们修改返回按钮的返回方法如下:

this.$router.goBack()
  • 第五步:监听路由变化

在这里我们使用到了 Vue 的 watch「这里不专门说,以后有需要会抽出来说」 方法,修改如下:

addWatchRouter

这样我们就可以监听是进入界面还是返回界面「这里 watch 和 data 方法是平级的,如果是返回的话,则从左边滑入到右边,打开新界面就是从右边滑到左边」

通过以上步骤,我们就给路由添加了一个过渡效果,我们来看看效果,就和刚开始的效果图是一样的

router-demo

这样我就完成了组件式开发中的路由的基本功能,结束了吗?这里本应该可以结束了,但是我们前面学过组件,并且 vue 的核心特点之一就是组件化开发,我们这里使用的头部功能「两个界面头部如下」

first-titlebar
second-titlebar.png

我们可以看到两个界面的头部是何其的相似,这里我们完全可以把这个头部封装成一个组件呀,下面我们继续改造我们的代码

封装头部组件

  • 1、新建一个 Head.vue 文件「核心代码」
custom-head-template
custom-head-js
  • 2、在 First.vue 和 Second.vue 中引入

分三步:

第一步:引入组件

import-head-component

第二步:注册组件

register-head-component

第三步:使用组件

use-head-component

想在那个 vue 组件中使用以上三步就可以搞定了

到这里我们就把 head 组件封装完成了,并且达到使用的上的,具体可以看源代码

到此为止,我们把 vue-router 就介绍完了,总结一下

三、总结

  • 路由的分类:前端路由和后端路由「区别和联系」
  • 使用 hash 和 history 分别实现前端路由
  • vue-router 的基本使用方法「使用 html 引入和模块化开发两种方式」
  • vue-router 的举例子「手机端开发 webapp」

点赞富一生,转发富五代,更多文章请关注我的微信公号来查阅

公众号:TigerChain

Vue系列
Web note ad 1