前言

路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动

路由器提供了两种机制:路由和转送

  • 路由是决定数据包从来源到目的地的路径
  • 转送是将输入端的数据转移到合适的输出端

—前端渲染与后端渲染

  • 后端路由阶段:由服务器处理 URL 和页面之间的映射关系

  • 前后端分离阶段:后端只提供 API 来返回数据,前端通过 Ajax 获取数据,并且通过 JavaScript 将数据渲染到页面中

**

  • 单页面应用(SPA)阶段:在前后端分离的基础上加了一层前端路由,由前端来维护一套路由规则

—前端路由方式

当 URL 发生改变时,不会向服务器重新请求资源(不刷新)

URL 的 hash

可以通过直接赋值 location.hash 来改变 href,但页面不会发生刷新
image.png
此时页面的 href 变为
image.png

HTML5 的 history

history 接口是 HTML5 新增的,它有五种模式改变 URL 而不刷新页面

  • pushState

使用 history.pushState() 也可以改变 href
image.png
此时页面的 href 变为
image.png

  • back

加载 history 列表中的前一个 URL(返回上一页)
image.png

  • forward

加载 history 列表中的下一个 URL(前进下一页)
image.png

  • replaceState

和 pushState 用法一样,区别在于 replaceState 不能返回页面

  • go

加载 history 列表中的某个具体页面

history.go(-2) 后退2页

history.go(-1) 等同于 history.back()

history.go(1) 等同于 history.forward()

安装和使用

安装

npm install vue-router —save

配置

  1. // src/router/index.js
  2. import Vue from 'vue'
  3. // 导入 vue-router
  4. import Router from 'vue-router'
  5. // 导入组件
  6. import HelloWorld from '@/components/HelloWorld'
  7. // 1.通过 vue.use() 传入路由插件
  8. Vue.use(Router)
  9. // 2.创建并导出路由对象
  10. export default new Router({
  11. // 配置路由和组件之间的映射关系
  12. routes: [
  13. {
  14. path: '/home',
  15. name: 'HelloWorld',
  16. component: HelloWorld
  17. }
  18. ]
  19. })
  20. // 3.将 router 对象挂载到 vue 实例
  1. // src/main.js
  2. import Vue from 'vue'
  3. import App from './App'
  4. // 导入 router
  5. // 当不指定文件名时,会自动查询目录下的 index 文件
  6. import router from './router'
  7. Vue.config.productionTip = false
  8. /* eslint-disable no-new */
  9. new Vue({
  10. el: '#app',
  11. // 挂载到 vue 实例中
  12. router,
  13. render: h => h(App)
  14. })

使用路由

  • 标签方式
    1. // src/App.vue
    2. <template>
    3. <div id="app">
    4. <!-- 最终会渲染为 <a> 标签 to 属性用于指定跳转的路径 -->
    5. <!-- 默认是使用 hash 改变 href -->
    6. <router-link to="/home">首页</router-link>
    7. <!-- 相当于一个占位符,决定渲染之后的位置 -->
    8. <router-view></router-view>
    9. </div>
    10. </template>

改变 router-link 的模式

// src/router/index.js
export default new Router({
  mode: 'history'
})
  • 代码方式
    export default {
    name: "App",
    methods: {
      homeClick() {
        // 不要使用 history,因为这样就绕过了 vue-roter
        // 同样也有 replace 方法
        this.$router.push('/home')
      }
    };
    

route-link

  • to 属性

**
用于指定跳转的路径

<router-link to="/home">首页</router-link>
  • tag 属性

tag 属性的值用于指定渲染后的标签,默认为 标签

<!-- 默认为渲染为 a 标签 -->
<router-link to="/home">首页</router-link>
<router-link to="/about" tag="button">关于</router-link>
  • replace 属性

replace 属性没有值,当指定了 replace 后,路由跳转将会使用 history.``replaceState() 的方式

<router-link to="/home" tag="button" replace>首页</router-link>
  • 默认 class

**
当使用 router-link 渲染后的标签处于 active 状态时,Vue 会自动为标签添加 router-link-exact-activerouter-link-active class

要想改变默认 class 可以使用 active-class 属性

<router-link to="/home" active-class="active">首页</router-link>

但是给第一个标签都添加属性很麻烦,可以在 router 的配置文件中更改默认 class

// src/router/index.js
export default new Router({
  linkActiveClass: 'active'
})

**

路由方法

  • router.push()等同于 <router-link :to="...">
  • router.replace()router.push 很像,唯一的不同就是,它不会向 history 添加新记录
  • router.go() 这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)

**

动态路由

我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:**

const router = new Router({
  routes: [
    // 动态路径参数以冒号开头
    { path: '/user/:id', component: User }
  ]
})

使用 v-bink 在跳转链接中动态拼接用户 id

<template>
  <div id="app">
    <router-link v-bind:to="'/user/' + id" replace>用户</router-link>
    <!-- 相当于一个占位符,决定渲染之后的位置 -->
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      userId: "lisi",
    };
  },
};
</script>

以上使用的是声名式,还可以使用编程式

const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

当匹配到一个路由时,参数值会被设置到this.$route.param

<template>
  <div>
    <h2>我是用户界面</h2>
    <p>我是用户的相关信息</p>
    <h2>用户ID:{{ Id }}</h2>
  </div>
</template>

<script>
export default {
  name: "User",
  computed: {
    Id() {
      // $route 拿到当前活跃的 route 对象
      return this.$route.params.userId;
    },
  },
};
</script>

路由懒加载

路由懒加载的主要作用就是将路由对应的组件打包成一个个的 js 代码块,只有在这个路由被访问到的时候才加载对应的组件

使用

// src/router/index.js
// 使用箭头函数
const HelloWorld = () => import('../components/HelloWorld')

export default new Router({
  routes: [
    {
      path: '/home',
      name: 'HelloWorld',
      component: HelloWorld
    }
  ]
})

一个组件打包成一个 js 代码
image.png

路由的嵌套

创建对应的子组件,并在路由映射中配置对应的子路由

// src/router/index.js
const Home = () => import('../components/Home')
const HomeNews = () => import('../components/HomeNews')
export default new Router({
  routes: [
    {
      path: '/home',
      name: 'HelloWorld',
      component: HelloWorld,
      children: [
        {
            path: '/news',
          component: HomeNews
        }  
      ]
    }
  ]
})

在组件内部使用 标签

<template>
  <div>
    <h2>我是首页</h2>
    <p>我是首页内容</p>
    <router-link to="/home/news">新闻</router-link>
    <router-view></router-view>
  </div>
</template>

参数传递

  • 在“动态路由”中使用 $route.params 传递参数

  • 使用 query 传递参数

通过标签传递参数

<template>
  <div id="app">
    <h2>我是App组件</h2>
    <router-link :to="{ path: '/home', query: { name: 'why', age: '18' } }">首页</router-link>
    <router-view></router-view>
  </div>
</template>

通过代码传递参数

<script>
export default {
  name: "App",
  methods: {
      homeClick: {
         this.$router.push({
            path: '/home',
          query: {
              name: 'why',
              age: 18
            }
        }) 
    }
     }
};
</script>

image.png
通过 $route.query 获取参数

<template>
  <div>
    <h2>我是用户界面</h2>
    <p>我是用户的相关信息</p>
    <h2>用户名:{{ $route.query.name }}</h2>
  </div>
</template>

$router 和 $route

视频:https://www.bilibili.com/video/BV15741177Eh?p=114

任何组件都继承自 Vue 类的原型,而 $router 是一个 Vue 中的一个属性(全局属性),不管在哪获取的都是路由文件里 new 出来的 router 对象
image.png
从构造函数的源码可以看到“前端路由方式”中的各种方法
image.png

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

可以看到 this.$route 对象中有 params 对象和 query 对象
image.png

导航守卫

官网

监听路由之间的跳转,并实现一些功能

全局守卫

  • 前置守卫

在路由跳转前做的一些操作

使用

// src/router/index.js
const router = new VueRouter({ ... })

// beforeEach 前置守卫
router.beforeEach((to, from, next) => {
  // 从 from 跳转到 to 之前
  document.title = to.matched[0].meta.title
  console.log(to);
  // next 必要要调用
  next()
})

注意当一个组件使用了组件嵌套时,meta 属性中将不会有数据,而是会放到 matched 中
image.png
meta 数据可以在路由映射关系中定义

const routes = [
  {
    path: '/home',
    name: 'HelloWorld',
    component: Home,
    meta: {
      title: '首页'
    }
  }
]
  • 后置勾子

在路由跳转后做的一些操作

使用

// src/router/index.js
const router = new VueRouter({ ... })

// afterEach 后置勾子
router.afterEach((to, from) => {
  // 从 from 跳转到 to 之后
})

路由独享守卫

你可以在路由配置上直接定义 beforeEnter 守卫:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

这些守卫与全局前置守卫的方法参数是一样的

组件内的守卫

你可以在路由组件内直接定义以下路由导航守卫:

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave
const Foo = {
  template: `...`,
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

keep-alive

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染

keep-alive 有三个属性:

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
  • max - 数字。最多可以缓存多少组件实例

详细使用可以查看官网

生命周期函数 created() 也只会执行一次, destroyed() 函数一次也不会执行

activated() 函数和 deactivated() 函数只有组件被 包裹起来了才有效

  • created() 组件被创建之后
  • destroyed() 组件被销毁之后
  • activated() 组件路由活跃时
  • deactivated() 离开组件路由时

    示例

保持路由状态

<template>
  <div id="app">
    <!-- 只有避免渲染才能保持状态 -->
    <keep-alive>
      <router-view></router-view>
    </keep-alive>
  </div>
</template>
<script>
export default {
  name: "Home",
  data() {
    return {
      // 用于记录状态
      path: "/home/news",
    };
  },
  // 路由活跃时
  activated() {
    this.$router.push(this.path);
  },
  // 离开组件路由之前
  beforeRouteLeave(to, from, next) {
    this.path = this.$route.path;
    next();
  },
};
</script>

属 性

  • include

字符串或正则表达式,只有匹配的组件会被缓存

  • exclude

字符串或正则表达式,任何匹配的组件都不会被缓存

<template>
  <div id="app">
    // exclude 值为组件的 name 属性,多个以逗号分开(不要加空格)
    <keep-alive exclude="profile">
      <router-view></router-view>
    </keep-alive>
  </div>
</template>

tab-bar 案例

1、把大致框架搭好(html、css)
2、提取成组件(子组件和父组件)
3、需要动态展示组件的地方使用插槽
4、配置 router
5、动态监听跳转链接使用父子通信(props)
6、通过 this.$route.path 可以判断该路由是否处于活跃状态