Vue2基础-Vue Router

294 阅读7分钟

写在前面:

1.本文是个人课上学习内容的总结和梳理,主要知识点来自于“开课吧”线上直播课程,以及Vue官方文档。

2.目前尚处于Vue乃至前端入门阶段,因此很多内容理解的不是很透彻,文章更多是用来学习记录而非干货分享。

因此,建议如果需要解决项目问题,还是去看一下其他大佬的文档已经vue官方文档(一手资料)

vue router

简介

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

本质

通过Vue Router建立起页面(视图)与URL之间的映射关系。

对象

  • router:路由实例对象
  • route:路由对象
    • 一个路由对象 (route object) 表示当前激活的路由的状态信息,包含了当前 URL 解析得到的信息,还有 URL 匹配到的路由记录 (route records)。

可以理解为,router是一组容器管理了一堆route,route管理的是当前URL和组件的映射关系。

安装

  • 通过vue cli提供的插件安装方式

    vue add @vue/router
    
  • npm手动安装

    vue i vue-router
    

路由

动态路由

上面提到了Vue Router的本质就是建立URL和组件的映射关系,往往实际应用场景中会出现需要匹配多个url的路由来映射到同一个组件上。比如需要每一个用户登陆后,携带用户id的url都应该映射到同一个用户组件上,这时就需要用到动态路由进行匹配:

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

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

这样使用冒号:标记的内容就是动态匹配的,后面的id参数值会自动写入到$route.params.id之中,可以全局访问到。

以上是动态路由的基本使用场景,但是还需要注意几个问题:

  • 匹配优先级。

    • 当我们以/user/开头写了好几个匹配规则之后,如果一个URL同时满足这些规则,那么哪个规则先定义就执行哪个规则。
  • 复用性问题。

    • 当复用路由参数时,例如从 /user/foo 导航到 /user/bar原来的组件实例会被复用。因为两个路由都渲染到同一个组件,比起销毁再创建,复用则显得更加高效。
    • 我们可以侦听watch $route的变化或者使用导航守卫beforeRouteUpdate,来解决这个问题。
    const User = {
      template: '...',
      watch: {
        $route(to, from) {
          // 对路由变化作出响应...
        }
      }
    }
    
    const User = {
      template: '...',
      beforeRouteUpdate (to, from, next) {
        // 对路由变化作出响应...
        // 一定要写上next(),否则进程会卡在这里
      }
    }
    

路由组件传参

我们之前解释过了,route是表示单一组件和url之间的映射关系,也就是$route只能在有限的url內使用,耦合性高,为了消除耦合性,我们可以使用props参数(老朋友了)进行传参。

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User, props: true },

    // 对于包含命名视图的路由,你必须分别为每个命名视图添加props选项:
    {
      path: '/user/:id',
      components: { default: User, sidebar: Sidebar },
      props: { default: true, sidebar: false }
    }
  ]
})

嵌套路由

根据实际应用场景,有时候需要将URL各段动态地址按照某种结构对应到视图的不同组件上,如下:

/user/foo/profile                     /user/foo/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

利用嵌套路由我们可以比较轻松的实现这种业务逻辑,但是不推荐嵌套层数超过两层,否则代码可读性太差不容易维护。

我们知道是我们路由的渲染出口,所有内容都会渲染到该标签內,那我们只要在上面的/user/组件的模版里面再加一层,并且使用route提供的children参数不是就轻松实现了嵌套路由么,实现代码如下:

const User = {
  template: `
    <div class="user">
      <h2>User {{ $route.params.id }}</h2>
      <router-view></router-view>
    </div>
  `
}

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        {
          // 当 /user/:id/profile 匹配成功,
          // UserProfile 会被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 当 /user/:id/posts 匹配成功
          // UserPosts 会被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

可以看到children参数內是嵌套组件的参数,所有options api都是照搬user组件的写法,所以理论上UserProfile、UserPosts组件还可以继续嵌套其他组件。不过还是那句话,为了代码的可维护性,2层嵌套就够了。

还需要注意的一点是,我们在children.path的配置时,是没有写/的,这样URL也是会自动补上/并拼在一起。不要自己补上/否则会被识别成根目录。

命名视图

有时候我们需要在同一个页面内有多个同级的渲染出口,这时候就需要给每一个起一个名字,告诉每一个出口他自己应该对应哪个一个视图组件。

<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>

在配置router的时候,需要给对应的组件命名清楚:(默认不起名字就是default)

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})

命名路由

就像我们给每一个视图组件起了个名字一样,往往我们也是需要给路由起个名字的,这样更方便我们调用。这种方式在后面会写到的导航当中,是十分常用的。

const router = new VueRouter({
  routes: [
    {
      path: '/user/:userId',
      name: 'user',
      component: User
    }
  ]
})

这样我们我们就给这个路由起好了名字啦,他就叫user,这样我们在导航中就可以方便的声明我们是要跳转到哪个路由上面。

重定向

我们可以利用重定向将/自动跳转到/home,但是要注意的一点是,所有导航守卫都是只针对跳转后的目标生效的,因此在/内定义的任何导航守卫都不会生效。

const router = new VueRouter({
  routes: [
    { path: '/', redirect: '/home' }
  ]
})

也可以重定向到一个命名路由上,只要告诉他路由的名字即可。

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'home' }}
  ]
})

别名

这个类似于重定向:

  • 重定向是从/跳转到了/home,路由匹配到的也是/home的路由组件
  • 别名是//jia根本就是一个东西的两个叫法而已,路由匹配到的还是/的路由组件
const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})

导航

上面说了这么多,都是用过操作URL来实现渲染不同的视图,不过我们怎么可能让用户用这种操作URL的方式呢,为此,vue router提供了一种名为导航的方式来给用户使用,分别是声明式和编程式。

声明式导航

<router-link> 组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a> 标签,可以通过配置 tag 属性生成别的标签.。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。

通过to参数来声明连接指向的组件

<!-- 字符串 -->
<router-link to="home">Home</router-link>

<!-- 使用 v-bind 的 JS 表达式 -->
<router-link v-bind:to="'home'">Home</router-link>

<!-- 渲染结果 -->
<a href="home">Home</a>

编程式导航

除了使用 <router-link> 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。主要有三种方法:

  • this.$router.push()

    • 上述声明式导航其实就是在底层调用了push方法,所以语法类似,可以传入路由名称、对象、以及一些参数。

      // 字符串
      router.push('home')
      
      // 对象
      router.push({ path: 'home' })
      
      // 命名的路由
      router.push({ name: 'user', params: { userId: '123' }})
      
    • push方法是玩history栈里面塞进去了一个新数据,不影响其他已经入栈的数据,所以在浏览器上是可以回退到上一级视图的。

  • this.$router.replace()

    • replace方法和push方法的语法一致,这里不进行赘述了。
    • 但是他对数据栈的操作是不一样的。replace方法是在数据栈中将当前路由替换为目标路由,调用完该方法之后,数据栈之中就没有了原来的路由组件,因此在浏览器上不可以回退到上一级视图。
  • this.$router.go()

    • go方法的语法就不太一样了,他是通过传入“步数”来控制路由的跳转的。这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)

      // 在浏览器记录中前进一步,等同于 history.forward()
      router.go(1)
      
      // 后退一步记录,等同于 history.back()
      router.go(-1)
      
      // 前进 3 步记录
      router.go(3)
      
      // 如果 history 记录不够用,那就默默地失败呗
      router.go(-100)
      router.go(100)
      

导航守卫

上面说完了导航的两种调用方式,那么可以看出,导航其实就是表示路由正在变化,从一个跳转到了另一个。

导航守卫就为我们提供了一些接口,让我在全局或者组件内部监控处于不同阶段的变化,并拦截特定的时间点在该点作出我们需要的逻辑动作:

  • 全局守卫
    • router.beforeEach
    • router.beforeResolve
    • router.afterEach
  • 路由独享守卫
    • beforeEnter
  • 组件守卫
    • beforeRouteEnter
    • beforeRouteUpdate
    • beforeRouteLeave

通过上述的API,我们可以在我们想要的导航阶段进行我们想要的逻辑处理,但是一定不要忘记在自己代码中中加上next()让导航继续执行下去,否则将会卡住不动。

关于导航守卫我准备单独写一篇文章,这里就不展开了,预留一个导航守卫的文章链接

模式

hash

使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。

history

我们可以在路由那设置成的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。(暂时还没时间看这个API的具体内容)

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

但是这种模式有一个弊端,就是需要后端良好的配合,否则当范围到某些在VueRouter上没有定义的URL的时候,就会抛出报错。