【VueRouter 源码学习】第六篇 - 路由匹配的实现

502 阅读4分钟

这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战


一,前言

上篇,介绍了两种路由模式的设计及初始化操作,主要涉及以下内容:

  • 创建两种路由模式类;
  • 父类和子类继承方法的设计;
  • 路由初始化 init 处理逻辑;

本篇,介绍路由匹配的实现;


二,承上启下

1,前文回顾

  • 执行 Vue.use 安装插件时,通过 Vue.mixin 向所有组件混入导出的 router 实例;
  • 将根组件上的 router 实例保存到_routerRoot上,使所有组件都能够通过父亲获取到根组件,即可以访问到同一个 router 实例;
  • 在 VueRouter 实例化时,通过路由匹配器 createMatcher 对路由配置进行扁平化处理;
  • 在路由匹配器中,创建了路由插件的两个核心方法:match 和 addRoutes;
  • 在 VueRouter 中创建了 history,存储不同模式下的子类逻辑的实现;
  • 在路由初始化时,会默认执行一次路由跳转 transitionTo 并在此时绑定路由变化的监听器,之后,当 Hash 再次变化时,将继续进行调跳转操作;

2,本篇介绍

所以,接下来就需要在跳转方法 transitionTo 中,根据当前路径获取到路由匹配的结果;


三,路由匹配的分析

前面,通过路由匹配器对路由配置进行了扁平化处理,示例:

{'/': Record1, '/user': Record2, '/user/info': Record3 }

接下来,进行路由匹配,如果访问的路径是:/user/info,路由匹配的结果将是两条:

// 先匹配到 /user 之后,才能匹配到 /user/info;
matches:[Record2, Record3]

所以,一个路径有可能同时对应多个匹配规则;

备注:matches数组中匹配到的多条记录将用于<router-view>的组件渲染;


四,路由匹配的实现

1,首次路由匹配

当页面加载完毕,路由进行初始化流程进入 init 方法,会默认触发一次路由跳转 transitionTo;此时需要进行一次路由匹配操作,即:根据当前页面路径 location 到 router 实例上的 matcher 中做匹配;

// history/base.js

/**
 * 路由基类
 */
class History {
  constructor(router) {
    this.router = router;
  }

  /**
   * 路由跳转方法:
   *  每次跳转时都需要知道 from 和 to
   *  响应式数据:当路径变化时,视图刷新
   * @param {*}} location 
   * @param {*} onComplete 
   */
  transitionTo(location, onComplete) {
    // 根据路径进行路由匹配
    let route = this.router.match(location);
    onComplete && onComplete();
  }
}

export { History }

2,router.match 的实现

在 VueRouter 类中添加 match 方法,提供路由跳转时调用:

/**
 * 根据路径匹配到路由映射表 matcher 中进行路由匹配
 * 备注:VueRouter 类通过 match 方法对外提供 matcher 的访问,而不是直接访问 matcher
 * @param {*} location 路径
 * @returns 匹配结果数组
 */
match(location) {
    // createMatcher.match
    return this.matcher.match(location);
}

备注:根据类的封装性设计原则,在 History 类方法中,不能直接 访问 VueRouter 类中的实例属性 matcher,需要通过类方法 match 方法对方提供数据访问;


3,matcher.match 的实现

路由匹配器 createMatcher 中 match 方法的实现:

import createRouteMap from "./create-route-map"
import { createRoute } from './history/base'

/**
 * 路由匹配器
 *  路由配置扁平化处理
 *  核心方法:addRoutes、match
 * @param {*} routes 
 * @returns 
 */
export default function createMatcher(routes) {

    let { pathMap } = createRouteMap(routes); //  路由配置扁平化处理

    function addRoutes(routes) {
        createRouteMap(routes, pathMap);
    }

    function match(location) {
        // 获取路由记录
        let record = pathMap[location]; // 一个路径可能有多个记录 
        // 匹配成功 
        if (record) {
            return createRoute(record, {
                path: location
            })
        }
        // 未匹配到 
        return createRoute(null, {
            path: location
        })
    }

    return {
        addRoutes, // 添加路由 
        match // 用于匹配路径
    }
}

4,createRoute 实现

let record = pathMap[location]匹配一条路由记录;

需要将路由记录拆分,并逐层进行路由匹配,通过 createRoute 得到匹配记录数组:

// history/base.js

/**
 * 通过路由记录,逐层进行路由匹配
 * @param {*} record    路由记录
 * @param {*} location  路径
 * @returns 逐层匹配后的全部匹配结果
 */
export function createRoute(record, location) {
  let res = []; //[/about /about/a] 
  if (record) {
    while (record) {
      res.unshift(record);
      record = record.parent;
    }
  }
  return {
    ...location,
    matched: res
  }
}

class History {
  constructor(router) {
    this.router = router;
    // {'/': Record1, '/user': Record2, '/user/info': Record3 }
    this.current = createRoute(null, {
      path: '/'
    });
  }

  /**
   * 路由跳转方法:
   *  每次跳转时都需要知道 from 和 to
   *  响应式数据:当路径变化时,视图刷新
   * @param {*}} location 
   * @param {*} onComplete 
   */
  transitionTo(location, onComplete) {
    // 根据路径进行路由匹配
    let route = this.router.match(location);
    onComplete && onComplete();
  }
}

export { History }

五,结尾

本篇,介绍路由匹配的实现,包括以下几个点:

  • 路由匹配的分析;
  • 路由匹配的实现:router.match、matcher.match、createRoute;

下一篇,待定