【VueRouter 源码学习】第五篇 - 两种路由模式的设计

281 阅读4分钟

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


一,前言

上篇,介绍创建路由映射表,主要包含以下内容:

  • 创建路由初始化方法 init;
  • 路由匹配器的创建 createMatcher;
  • match方法 和 addRoutes方法的实现;

本篇,继续介绍两种路由模式的设计;


二,承上启下

1,前文回顾

  • 在 VueRouter 实例化时,通过路由匹配器 createMatcher 对路由配置进行扁平化处
  • 在路由匹配器中,创建了路由插件的两个核心方法:match 和 addRoutes;

2,本篇介绍

有了路由配置的映射关系之后,还需要实现根据不同路径跳转并显示对应的组件; 本篇,主要介绍两种路由模式的设计及初始化操作;

三,路由跳转的实现

1,路由模式的处理

根据路由选项中所配置的不同路由模式,需要进行不同的处理;

路由模式共有三种,主要介绍 Hash 和 History 两种模式:

// index.js

import HashHistory from './history/hash';
import BrowserHistory from './history/history';

class VueRouter {
    constructor(options) {
        // 路由配置的扁平化处理
        this.matcher = createMatcher(options.routes || []);
        // 根据不同的路由模式,生成对应的处理实例
        options.mode = options.mode || 'hash'; // 默认hash模式
        switch (options.mode) {
            case 'hash':
                this.history = new HashHistory(this);
                break;
            case 'history':
                this.history = new BrowserHistory(this);
                break;
        }
    }
    // 路由初始化方法,供 install 安装时调用
    init(app) {}
}
VueRouter.install = install;
export default VueRouter;

路由切换时,需要通过 matcher 进行规则匹配,所以在创建两种理由模式的类时,需要传入 this,即当前路由实例;

2,创建两种路由模式类:HashHistory 和 BrowserHistory

创建两种路由模式对应的类:HashHistory 和 BrowserHistory,他们均继承自路由的公共逻辑处理 History 类;

History 类:HashHistory 和 BrowserHistory 的父类,包含两种路由模式的公共逻辑处理:

// history/base.js
class History {
  constructor(router) {
    this.router = router;  // 存储子类传入的 router 实例
  }
}

export { History }

HashHistory 类:继承自 History 类,对应 Hash 模式下方法的实现:

// history/hash.js
import { History } from "./base";

class HashHistory extends History{
  constructor(router) {
    super(router);    // 调用父类构造方法,并将 router 实例传给父类
    this.router = router;  // 存储 router 实例,共内部使用
  }
}

export default HashHistory

BrowserHistory 类:继承自 History 类,对应 History 模式下方法的实现:

// ---------------------------- // 
// history/hash.js
import { History } from "./base";

class BrowserHistory extends History{
  constructor(router) {
    super(router);    // 调用父类构造方法,并将 router 实例传给父类
    this.router = router;  // 存储 router 实例,共内部使用
  }
}

export default BrowserHistory

3,Hash 模式的路径处理

Hash 模式下,路径后会默认携带'/'符号,HashHistory 初始化时进行处理;

import { History } from "./base";

function ensureSlash() {
  // location.hash 存在兼容性问题,可根据完整 URL 判断是否包含'/'
  if (window.location.hash) {
    return;
  }
  window.location.hash = '/'; // 如果当前路径没有hash,默认为 /
}

class HashHistory extends History {
  constructor(router) {
    super(router);
    this.router = router;
    // Hash 模式下,对URL路径进行处理,确保包含'/'
    ensureSlash();
  }
}
export default HashHistory

4,父类和子类的设计

  • 无论是哪一种路由模式 HashHistory 或 BrowserHistory 都需要通过当前的路径进行路由匹配和跳转
  • 路径获取:Hash 模式获取当前路径 hash 值,history 模式获取当前路径 path 值;
  • 路由监听:Hash 模式监听 hashchange 事件,history 模式监听 popState 事件;

父类和子类的方法设计:

  • base.js:
    • transitionTo 根据路由进行匹配跳转;
  • hash.js:
    • getCurrentLocation:获取当前路径 hash 值;
    • setupListener:监听 hashchange 事件
  • history.js:
    • getCurrentLocation:获取当前路径 path 值;
    • setupListener:监听 popState 事件

5, 父类和子类方法的实现

父类 History:

// base.js

class History {
    constructor(router) {
        this.router = router;
    }
    // 根据路径进行路由匹配,并添加路径改变的监听器
    transitionTo(location, onComplete) {
      onComplete && onComplete();
    }
}

export { History }

Hash 模式的子类 HashHistory:

import { History } from "./base";

class HashHistory extends History{
    constructor(router){
        super(router);
        this.router = router;
        ensureSlash();
    }
    getCurrentLocation(){
        // 获取路径的 hash 值
        return getHash();
    }
    setupListener(){
        // 当 hash 值变化时,获取新的 hash 值,并进行匹配跳转
        window.addEventListener('hashchange',()=>{
            this.transitionTo(getHash());
        })
    }
}

export default BrowserHistory

History 模式的子类 HashHistory:

class BrowserHistory extends History{
    setupListener(){
        // 当路径变化时,拿到新的 hash 值,并进行匹配跳转
        window.addEventListener('popState',()=>{
            this.transitionTo(getHash());
        })
    }
}

export default BrowserHistory

6,路由初始化 init 方法

// index.js

class VueRouter {
    constructor(options) {
        this.matcher = createMatcher(options.routes || []);
        options.mode = options.mode || 'hash';
        switch (options.mode) {
            case 'hash':
                this.history = new HashHistory(this);
                break;
            case 'history':
                this.history = new BrowserHistory(this);
                break;
        }
    }
    // 监听 hash 值变化,跳转到对应的路径中
    init(app) {
        // 当前的history实例:可能是HashHistory,也可能是BrowserHistory;
        const history = this.history;
        // 设置监听器:内部调用的是不同子类中的实现
        const setUpListener = () => {
            history.setupListener();
        }
        // 初始化时,获取当前hash值进行跳转, 并设置监听器
        history.transitionTo(
            history.getCurrentLocation(),
            setUpListener
        )
    }
}
VueRouter.install = install;

export default VueRouter;

四,结尾

本篇,介绍了两种路由模式的设计及初始化操作,主要涉及以下几个点:

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

下一篇,路由匹配的实现;


维护日志

  • 20210822:
    • 更新了文章的标题和摘要;