Vue-Router 源码学习之我们从API中看些门道

679 阅读5分钟

前言

这次真的是太久太久没见了,伙伴们。 学习真的没有松懈,只是季度末要冲一冲绩效,一下子把时间都充值了。(ps:我看应该是我的脑子需要充值)

系列文章: Vue-Router源码分析之index.js

Vue-Router源码分析之install方法

正文

为什么要从API中看门道呢?

我们在使用vue-router的时候,其实就是按照API进行操作,源码其实就是API的另一种形式,在我的学习中,我发现一味的干货有时效果并不好,由其是深入思考性比较强的内容。因此本篇文章所以作为一个过渡章节,让我们一起聊一聊承上启下的内容,聊聊API:

我们的push有哪些情况?

在实际工作中,编程式导航要比<router-link>标签使用的多的多,复杂逻辑中嵌套路由跳转都是要使用编程式导航。

// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})
router.push({ path: `/user/${userId}` })
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

在编程式路由中,我们可以接收一个对象、一个字符串,对象的key有path、name、query、params等等,但是需要相互组合。

这么多情况vue-router要对push这么多情况做一个处理,我们push接收的参数的类型是什么?用flow设定为Location、RawLocation

用flow.js设定的类型
declare type Location = {
  _normalized?: boolean;
  name?: string;
  path?: string;
  hash?: string;
  query?: Dictionary<string>;
  params?: Dictionary<string>;
  append?: boolean;
  replace?: boolean;
}
declare type RawLocation = string | Location

翻译过来就是未处理的、生的路径。所以我们的push操作传递的是一个生冷的路径,经过一次match的处理

源码内容:
const route = this.router.match(location, this.current)

变成vue-router所需要的结构,route类型

declare type Route = {
  path: string;
  name: ?string;
  hash: string;
  query: Dictionary<string>;
  params: Dictionary<string>;
  fullPath: string;
  matched: Array<RouteRecord>;
  redirectedFrom?: string;
  meta?: any;
}

所以从API结合表层源码来看,我们在进行编程式导航时的过程,先将未处理的路径(ps:vue-router对这种未处理路径没法直接进行跳转等操作)处理成一个规范的route类型,然后进行具体的路由跳转内容。

命名路由与视图

<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>

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

一个组件内可以有多个router-view组件,我接触的几个项目都是维护一个根结点的router-view。大部分都是这种结构

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

我们一个路径下可以展示多个路由视图组件吗?

答案当然是肯定的,官方的命名视图的例子(jsfiddle.net/posva/22wgk… 可以看到,我们在同一个路径下可以放置多个<router-link>,这里面的name与我们在构造vue-router时设置的components有对应,我们一般都是component : xxx;对应一个组件。如何对应多个的呢?

源码内容:
components: route.components || { default: route.component },

其实人家本身希望你用components,你要只用一个人家就给你包装成一个map,key呢就是默认值的意思。

再看看我们的vue-router2开始的路由守卫

路由守卫有哪些呢?

  • 1:全局的beforeEach

  • 2:单个路由配置的beforeEnter

  • 3:全局的afterEach

  • 4:组件内的路由守卫:

    beforeRouteEnter

    beforeRouteUpdate (2.2 新增)

    beforeRouteLeave

  • 5:全局的beforeResolve(vue-router2.5新增)

别的守卫还有吗?这么看来是没有了。

全局的beforeEnter、afterEach、beforeResolve为什么会作用在每一个路由配置中呢?在index一章中,我们有三个存放守卫的数组大家还有印象吗?

beforeHooks: Array<?NavigationGuard>;
resolveHooks: Array<?NavigationGuard>;
afterHooks: Array<?AfterNavigationHook>;

在router实例中存储这些。所以我们在做路由跳转的时候可以拿到这些守卫,其余的守卫怎么办?只好在跳转的时候具体情况具体执行,

守卫分为三个种类:

1:离开组件之前,2:进入组件(前后都有守卫) 3:更新组件之前

所以一个路由的跳转一定伴随着以下几个步骤

  • 1:跳转我们称之为transitionTo

  • 2:那么跳转中进行一个confirmTransiton(跳转前的准备)

  • 3:跳转结束时进行一个updateRoute的过程

上文提到的confirmTransition:先收集到所有的守卫,把前置守卫们(前置!!!)连接成一个数组,跳转前挨个执行,前置守卫执行完怎么办?都通过来,那就更新当前路由,更新完当前路由就执行一下后置守卫。 vue-router源码中:

核心的路由跳转方法就是transitionTo: 具体内容是由confirmTransition与updateRoute实现的

其余的一些这这那那的容错,并不影响主流程。

总结一下

对于push、replace传递的未处理的路径,进行处理成vue-router可以操作的路径。

对于多个router-view,我们可以选择children,components来实现。

路由守卫为什么可以有全局的,每个路由都可以用,因为在router实例上,我们每次跳转都可以找到。

路由跳转经历了confirm确认与update更新两步

结束语

中秋节刚刚过去,十一马上就到了

没有来得及给大家带来祝福,希望大家十一的时候每次路由跳转都不堵车!

每一个前端er(boy and girl) 你们都不是一个人在战斗

我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,已经我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

个人公众号:IT面试填坑小分队