vue (六) - Vue Router

160 阅读6分钟

Vue Router 是 Vue.js 的官方插件,用来快速实现单页应用。

需要应用时就引入, 不需要时就不用引用.

单页应用

SPA(Single Page Application)单页面应用程序,简称单页应用。指的是网站的 “所有” (大部分或某部分)功能都在单个页面中进行呈现。

具有代表性的有后台管理系统、移动端、小程序等。

  • 如果一个页面只有一个功能 - 多页应用 (传统方式) - 主要是电商

单页应用优点

  • 前后端分离开发,提高了开发效率。

  • 业务场景切换时,局部更新结构。

  • 用户体验好,更加接近本地应用。

单页应用缺点

  • 不利于 SEO。

  • 初次首屏加载速度较慢。

  • 页面复杂度比较高。

前端路由

前端路由,指的是 URL 与内容间的映射关系

  • 三要素: URL、内容、映射关系

两种实现方式:

  • Hash 方式
  • History 方式

Hash 方式

通过 hashchange 事件监听 hash 变化,并进行网页内容更新。

hash是url的一部分 (#后面的部分), hash会修改url但是不会跳转.

<body>
  <div>
    <a href="#/">首页</a>
    <a href="#/category">分类页</a>
    <a href="#/user">用户页</a>
  </div>
  <div id="container">
    这是首页功能
  </div>

  <script>
    var container = document.getElementById('container');

    window.onhashchange = function () {
      // 获取 url 中的 hash
      var hash = location.hash.replace('#', '');
      // 根据不同 hash 值,更改网页内容(功能)
      var str = '';
      switch (hash) {
        case '/':
          str = '这是首页的功能';
          break;
        case '/category':
          str = '这是分类的功能';
          break;
        case '/user':
          str = '这是用户的功能';
          break;
      }
      container.innerHTML = str;
    };
  </script>
</body>
  • location.hash得到的hash值是带#的, 所以用replace去除hash值.

hash封装

封装以备复用。

  • router就是我们准备的路由功能的对象
  • routes是个对象, 用来存储多个路由, 即url(hash)和内容处理函数的对应关系
  • route是用来定义路由的函数, path是路径, callback是功能.
  • init方法用来进行路由的初始化操作,
  • &&只有前面的一个为true时才能执行第二个
<body>
  <div>
    <a href="#/">首页</a>
    <a href="#/category">分类页</a>
    <a href="#/user">用户页</a>
  </div>
  <div id="container">
    这是首页功能
  </div>

  <script>
    // 准备对象,用于封装 hash 功能。
    var router = {
      // 路由存储位置: 保存了 url 与 内容处理函数的对应关系。
      routes: {},
      // 定义路由规则的方法
      route: function (path, callback) {
        this.routes[path] = callback;
      },
      // 初始化路由的方法
      init: function () {
        var that = this;
        window.onhashchange = function () {
          // 当 hash 改变,我们需要得到当前新的 hash
          var hash = location.hash.replace('#', '');
          // 根据 hash 触发 routes 中的对应 callback
          that.routes[hash] && that.routes[hash]();
        };
      }
    };

    var container = document.getElementById('container');
    // 定义路由规则
    router.route('/', function () {
      container.innerHTML = '这是首页功能';
    });
    router.route('/category', function () {
      container.innerHTML = '这是分类功能';
    });
    router.route('/user', function () {
      container.innerHTML = '这是用户功能';
    });

    // 初始化路由
    router.init();
  </script>
</body>

Hash 方式特点总结

  • Hash 方式兼容性好。

  • 地址中具有 #,不太美观。

  • 前进后退功能较为繁琐。

History 方式

History 方式采用 HTML5 提供的新功能实现前端路由。兼容性没有hash好.

在操作时需要通过 history.pushState() 变更 URL并执行对应操作。

image.png

image.png

history.pushState()的参数:

  • 参数一: 与当前路径相关的状态对象
  • 参数二: 用来传入标题(目前浏览器不支持)
  • 参数三: 当前希望将url设置为哪个路径, 但不会出现跳转操作

image.png

  • 直接用.href属性获取到的是整个url的值
  • getAttribute()获取的是href属性它内部的值, 也就是"/", "/category", "/user"这些值.

image.png

前进后退处理

  • 前进后退功能,首先需要在更改 url 时保存路由标记。更改pushState()的第一个参数.
  • 通过 popstate 事件监听前进后退按钮操作,并检测 state, 如果有就去到相应的路径, 没有就返回首页。
  • router.init()调用初始化方法监听前进后退操作并处理。
<body>
  <div>
    <a href="/">首页</a>
    <a href="/category">分类</a>
    <a href="/user">用户</a>
  </div>
  <div id="container">
    这是首页功能
  </div>

  <script>
    var router = {
      // 存储路由的对象
      routes: {},
      // 定义路由的方法
      route (path, callback) {
        this.routes[path] = callback;
      },
      // 用于触发指定的路由操作
      go (path) {
        // 更改 url
        history.pushState({ path: path }, null, path);
        // 触发路由对应的回调函数
        this.routes[path] && this.routes[path]();
      },
      // 设置初始化方法,用来检测前进后退按钮的功能
      init () {
        var that = this;
        window.addEventListener('popstate', function (e) {
          var path = e.state ? e.state.path : '/';
          that.routes[path] && that.routes[path]();
        });
      }
    };

    router.init();

    // 设置 a 标签的功能
    var links = document.querySelectorAll('a');
    var container = document.querySelector('#container');

    links.forEach(function (ele) {
      ele.addEventListener('click', function (event) {
        router.go(this.getAttribute('href'));
        event.preventDefault();
      });
    });

    // 路由规则
    router.route('/', function () {
      container.innerHTML = '首页功能';
    });

    router.route('/category', function () {
      container.innerHTML = '分类功能';
    });

    router.route('/user', function () {
      container.innerHTML = '用户功能';
    });
  </script>
</body>

Vue Router

是 Vue.js 官方的路由管理器,让构建单页面应用变得易如反掌。

基本使用

如何安装vue router?

  1. 直接下载 / CDN
  1. npm
npm install vue-router

如果是引入的形式, 要在vue.js后面引用. image.png

基本结构

Vue Router 提供了用于进行路由设置的组件 <router-link><router-view>

  • <router-link> 用于切换,使用to设置路径; 默认是<a>标签, 如果希望是其他形式(比如<li>), 可以使用tag更改.

  • <router-view>用来显示路由匹配到的组件

image.png

具体操作

  1. 定义路由中需要的组件,并进行路由规则设置。

image.png

  1. 定义路由中需要的组件,并进行路由规则设置。

image.png

  1. 创建 Vue Router 实例,通过 routes 属性配置路由。

image.png

  1. 创建 Vue 实例,通过 router 属性注入路由。

image.png

命名视图

<router-view>只能显示一个视图, 如果导航后,希望同时在同级展示多个视图(组件),这时就需要进行命名视图 (用于区分不同的视图)。

  • 有一个或多个<router-view>时只能有一个不命名, 默认为name="default"

image.png

路由中通过 components 属性进行设置不同视图的对应组件:

  • 左边(如default)指的是: <router-view>name
  • 右边(如SideBar1)指的是: 组件的名称

image.png

<body>
  <div id="app">
    <router-link to="/">首页</router-link>
    <router-link to="/user">用户</router-link>

    <router-view name="sidebar"></router-view>
    <!-- 没有设置 name 的 router-view 默认 name 为 default-->
    <router-view></router-view>
  </div>
  <script src="lib/vue.js"></script>
  <script src="lib/vue-router.js"></script>
  <script>
    var SideBar1 = {
      template: `<div>侧边栏1功能</div>`
    };

    var SideBar2 = {
      template: `<div>侧边栏2功能</div>`
    };

    var Index = {
      template: `<div>首页功能</div>`
    };

    var User = {
      template: `<div>用户功能</div>`
    };

    // 定义路由规则
    var routes = [
      {
        path: '/',
        components: {
          // router-view 的 name : 组件配置对象
          default: Index,
          sidebar: SideBar1
        }
      },
      {
        path: '/user',
        components: {
          default: User,
          sidebar: SideBar2
        }
      }
    ];

    // 创建 Vue Router 实例
    var router = new VueRouter({
      routes
    });

    // 创建 Vue 实例
    new Vue({
      el: '#app',
      router
    });
  </script>

动态路由

当我们需要将某一类 URL 都映射到同一个组件,就需要使用动态路由。

定义路由规则时,将路径中的某个部分使用 : 进行标记,即可设置为动态路由。

image.png

设置为动态路由后,动态部分为任意内容均跳转到同一组件。比如用户1,2,3都会跳转到同一个页面:

image.png

: 部分对应的信息称为路径参数,存储在 vm.$route.params 中, 如果设置动态路由时使用的是:id, 那么这里就用.id获取动态变化内容.

  • 这里$route.params.id

image.png

<body>
  <div id="app">
    <router-link to="/user/1">用户1</router-link>
    <router-link to="/user/2">用户2</router-link>
    <router-link to="/user/3">用户3</router-link>

    <router-view></router-view>

  </div>
  <script src="lib/vue.js"></script>
  <script src="lib/vue-router.js"></script>
  <script>
    // 设置组件 
    var User = {
      template: `<div>这是用户 {{ $route.params.id }} 的功能</div>`
    };

    // 设置路由规则
    var routes = [
      {
        path: '/user/:id', component: User
      }
    ];

    var router = new VueRouter({ routes });
    var vm = new Vue({
      el: '#app',
      router
    });
  </script>
</body>

image.png

image.png

侦听路由参数

如果要响应路由的参数变化,可以通过 watch 监听 $route

  • 动态路由切换时, 组件是复用而不是重新创建.

image.png

  • 当切换不同的用户时, $route就会发生变化, 就会调用watch, 这是route的内容如下. 其中route.params.id就是对应的动态路由的内容.

image.png

路由传参处理 (单个视图)

通过路由的 props 设置数据,并通过组件 props 接收。

发送方:

  • 如果设置了props:true, 接收的时候就使用 props接收. 实际上是把$route.params.id的值传到了props

image.png

接收方: image.png

<body>
  <div id="app">
    <router-link to="/user/1">用户1</router-link>
    <router-link to="/user/2">用户2</router-link>
    <router-link to="/user/3">用户3</router-link>

    <router-link to="/category/1">分类1</router-link>
    <router-link to="/category/2">分类2</router-link>
    <router-link to="/category/3">分类3</router-link>

    <router-view></router-view>
  </div>
  <script src="lib/vue.js"></script>
  <script src="lib/vue-router.js"></script>
  <script>
    // 组件的配置对象
    var User = {
      template: `<div>这是用户 {{ $route.params.id }} 功能</div>`
    };

    var Category = {
      props: ['id'],
      template: `<div>这是分类 {{ id }} 功能</div>`
    };

    // 设置路由规则
    var routes = [
      {
        path: '/user/:id',
        component: User
      },
      {
        path: '/category/:id',
        component: Category,
        props: true
      }
    ];

    var router = new VueRouter({ routes });
    var vm = new Vue({
      el: '#app',
      router
    });
  </script>
</body>

路由传参处理 (多个视图)

包含多个命名视图时,需要将路由的 props 设置为对象

  • 如果组件中不需要传值, 可以写成default或者不写.

  • 如果希望设置静态数据,可将 props 中的某个组件对应的选项设置为对象,内部属性会绑定给组件的 props。

image.png

image.png

<body>
  <div id="app">
    <router-link to="/user/1">用户1</router-link>
    <router-link to="/user/2">用户2</router-link>
    <router-link to="/user/3">用户3</router-link>

    <router-link to="/category/1">分类1</router-link>
    <router-link to="/category/2">分类2</router-link>
    <router-link to="/category/3">分类3</router-link>

    <router-view name="sidebar"></router-view>
    <router-view name="sidebar2"></router-view>
    <router-view></router-view>
  </div>
  <script src="lib/vue.js"></script>
  <script src="lib/vue-router.js"></script>
  <script>
    var SideBar = {
      template:`<div>这是SideBar组件</div>`
    };

    var SideBar2 = {
      props: ["a","b"],
      template:`<div>sidebar2: {{a}} {{b}}</div>`
    };

    var User = {
      template:`<div>这是用户{{$route.params.id}}的组件</div>`
    };

    var Category = {
      props: ['id'],
      template: `<div>这是分类{{id}}的组件</div>`
    };

    var routes = [
      {
        path: "/user/:id",
        components: {
          sidebar: SideBar,
          default: User
        }
      },
      {
        path: "/category/:id",
        components: {
          sidebar: SideBar,
          sidebar2: SideBar2,
          default: Category
        },
        props: {
          sidebar: false, 
          sidebar2: {
            a: "状态1",
            b: "状态2"
          },
          default: true
        }
      }
    ];

    var router = new VueRouter({ routes })
    var vm = new Vue({
      el: "#app",
      router
    })
  </script>
</body>

image.png

image.png

嵌套路由

实际场景中,路由通常由多层嵌套的组件组合而成,这时需要使用嵌套路由配置。

  • 使用 children 来进行嵌套路由中的子路由设置。

image.png

<body>
  <div id="app">
    <router-link to="/user">用户功能</router-link>
    <router-view></router-view>
  </div>
  <script src="./lib/vue.js"></script>
  <script src="./lib/vue-router.js"></script>
  <script>
    var User = {
      template: `
        <div>
          <h3>这是 User 组件的功能</h3>
          <router-link to="/user/hobby">爱好功能</router-link>
          <router-link to="/user/info">用户信息</router-link>
          <router-view></router-view>
        </div>
      `
    };

    var UserHobby = {
      template: `<div> UserHobby 组件</div>`
    };

    var UserInfo = {
      template: `
        <div> 
          UserInfo 组件
          <router-link to="/user/info/school">学校信息</router-link>
          <router-view></router-view>
        </div>`
    };

    var UserInfoSchool = {
      template: `<div> UserInfoSchool 组件</div>`
    };

    var routes = [
      {
        path: '/user',
        component: User,
        children: [
          {
            path: 'hobby',
            component: UserHobby
          },
          {
            path: 'info',
            component: UserInfo,
            children: [
              {
                path: 'school',
                component: UserInfoSchool
              },
            ]
          }
        ]
      }
    ];

    var router =  new VueRouter({ routes });
    var vm = new Vue({
      el: '#app',
      router
    });
  </script>
</body>

编程式导航

编程式导航,指的是通过方法设置导航。

  • router.push() 用来导航到一个新 URL (在控制台中可以进行测试)。

image.png

  • <router-link>to 属性使用绑定方式(v-bind)时也可以使用属性对象结构。使用v-bind绑定后, 实际上to会执行router.push()方法, 所以内部可以使用属性对象结构.

image.png

命名路由

设置路由时添加 name 属性。

image.png

push() 中通过 name 导航到对应路由,参数通过 params 设置。

注意: params用来设置路由的参数, 它只能与name搭配; 如果采用path的话, 必须采用完整的url地址.

image.png

也可以在 <router-link> 中使用:

image.png

<body>
  <div id="app">
    <router-link :to="{ name: 'school', params: { id: 10, a:2, b:3 } }">学校10</router-link>

    <router-view></router-view>
  </div>
  <script src="lib/vue.js"></script>
  <script src="lib/vue-router.js"></script>
  <script>
    var School = {
      template: `<div>School 组件的功能: {{ $route.params }}</div>`
    };

    var routes = [
      {
        path: '/user/:id/info/school',
        name: 'school',
        component: School
      }
    ];

    var router = new VueRouter({ routes });
    var vm = new Vue({
      el: '#app',
      router
    });
  </script>
</body>

image.png

重定向

image.png

别名(美化路由)

  • alias让显示在客户端浏览器上的url更简短和美观, 而实际上访问的是另一个比较复杂的url.

image.png

image.png

<body>
  <div id="app">
    <router-link :to="{name: 'school', params: {id:1, data:'0315'}}">学校信息</router-link>
    <router-link to="/20/1234">学校信息2</router-link>
    <router-view></router-view>
  </div>
  <script src="lib/vue.js"></script>
  <script src="lib/vue-router.js"></script>
  <script>
    // 组件
    var School = {
      template: `
        <div>School 组件</div>
      `
    };

    // 路由规则
    var router = new VueRouter({
      routes: [
        {
          path: '/user/:id/info/school/:date',
          name: "school",
          component: School,
          alias: ":id/:data"
        }
      ]
    });

    var vm = new Vue({
      el: '#app',
      router
    });
  </script>
</body>

设置别名后, 点击"学校信息2"时url会显示如下:

image.png

导航守卫

比如有一些页面需要对用户的访问权限进行限制, 需要拦截或进行跳转处理, 就需要导航守卫.

  • to 表示要跳转到的路由
  • from 是从哪里来的路由
  • next 如果fromto满足条件, 进行的下一步操作 next除了放行之外还可以传入一些参数, 比如当用户不能继续进行导航操作的时候, 可以传入false; 如果未登录用户访问了一些需要登陆以后才能进行的操作, 就跳转到登录页面.

image.png

  1. 如果next()直接调用, 表示可以执行后续功能.

image.png

  1. 设置next(false)表示阻止了后续操作.

image.png

  1. 路由跳转操作

比如说当点击"用户"(/user)的时候跳转到"分类"(/category)

image.png

History 模式

大部分url是使用hash模式的, 因为它的兼容性更好, 主要特征就是url中带有#号.

除此以外, url还有history模式(url中没有#了), 需要通过 Vue Router 实例的 mode 选项来设置,这样 URL 会更加美观,但同样需要后端支持避免问题。

image.png