vue-router 源代码全流程分析「长文」

说明

以下内容均是依托 vue-router 2.0.0 版本展开分析。
此篇文章,是自己对 vue-router 实现分析的记录性文章,如有任何错误,欢迎指正,相互交流。

基础使用
import Vue from ‘vue’;
import VueRouter from ‘vue-router’;

Vue.use(VueRouter);

const Home = { template: ‘

home
’ };
const Foo = { template: ‘
foo
’ };
const Bar = { template: ‘
bar
’ };
const Child = { template: ‘
Child
’ };

const router = new VueRouter({
mode: ‘history’,
// base: __dirname,
base: ‘/’, // 默认 ‘/’
routes: [
{ path: ‘/’, component: Home },
{ path: ‘/foo’, component: Foo },
{
path: ‘/bar’,
component: Bar,
children: [{ path: ‘child’, component: Child }]
}
]
});

const template = `

Basic

  • /
  • /foo
  • /bar
  • /bar
`;

new Vue({
router,
template
}).$mount(’#app’);
复制代码根据上述基础使用,我们大概可以梳理一个基本流程出来:

注册插件:

将 $router 和 $route 注入所有启用路由的子组件。
安装 和 。

定义路由组件。
new VueRouter(options) 创建一个路由器, 传入相关配置。
创建并挂载根实例,确保注入路由器。路由组件将在 中呈现。

下面我们就根据上述流程步骤,一步一步解析,vue-router 代码实现。
注册插件(vue-router)
首先 Vue.use(VueRouter); 这段代码间接执行 VueRouter 暴露的 install 方法,下面来看看 install 具体实现:(由于有完整的注释,故略去文字叙述)
install
import View from ‘./components/view’;
import Link from ‘./components/link’;

/**

  • 安装 Vue.js 插件 install 方法调用时,会将 Vue 作为参数传入。
  • @export
  • @param {*} Vue
  • @returns

*/
export function install(Vue) {
// 防止插件被多次安装 - 当 install 方法被同一个插件多次调用,插件将只会被安装一次。
if (install.installed) return;
install.installed = true;

// 在 Vue 原型上添加 r o u t e r 属 性 ( V u e R o u t e r ) 并 代 理 到 t h i s . router 属性( VueRouter )并代理到 this. router(VueRouter)this.root._router
Object.defineProperty(Vue.prototype, 'KaTeX parse error: Expected '}', got 'EOF' at end of input: … return this.root._router;
}
});

// 在 Vue 原型上添加 r o u t e 属 性 ( 当 前 路 由 对 象 ) 并 代 理 到 t h i s . route 属性( 当前路由对象 )并代理到 this. route()this.root._route
Object.defineProperty(Vue.prototype, 'KaTeX parse error: Expected '}', got 'EOF' at end of input: … return this.root._route;
}
});

// 全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。
Vue.mixin({
/**
* 混入 Vue 创建前钩子
* 1.取传入 Vue 构造函数的路由配置参数并调用 init 方法。
* 2.在 Vue 根实例添加 _router 属性( VueRouter 实例)
* 3.执行路由实例的 init 方法并传入 Vue 实例
* 4.把 (KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (this.options.router) {
this._router = this.$options.router;
this._router.init(this);
Vue.util.defineReactive(this, ‘_route’, this._router.history.current);
}
}
});

// 注册全局组件
Vue.component(‘router-view’, View);
Vue.component(‘router-link’, Link);
}
复制代码上述 就是 vue-router 暴露给 Vue 的注册方法。这里特别说明一下:defineReactive Vue 构建响应式的核心方法。在研究注册的两个全局组件: 和 之前,我们先讨论 VueRouter 构造函数,因为它们其中涉及到 VueRouter 的很多方法。
代码接着执行,接下来就是为 vue-router 装填配置并实例化。之后把实例化的结果传入 Vue 。
const router = new VueRouter({
// 选择路由模式
mode: ‘history’,
// 应用的基路径。默认值: “/” 例如,如果整个单页应用服务在 /app/ 下,然后 base 就应该设为 “/app/”.
base: ‘/’, // 默认 ‘/’
// 路由配置表
routes: [
{ path: ‘/’, component: Home },
{ path: ‘/foo’, component: Foo },
{
path: ‘/bar’,
component: Bar,
children: [{ path: ‘child’, component: Child }]
}
]
});

new Vue({
router
}).$mount(’#app’);
复制代码接下来我们就来看看定义路由的 VueRouter 构造函数。
定义路由:VueRouter 构造函数
/* @flow */

import { install } from ‘./install’;
import { createMatcher } from ‘./create-matcher’;
import { HashHistory } from ‘./history/hash’;
import { HTML5History } from ‘./history/html5’;
import { AbstractHistory } from ‘./history/abstract’;
import { inBrowser, supportsHistory } from ‘./util/dom’;
import { assert } from ‘./util/warn’;

export default class VueRouter {
static install: () => void;

app: any; // Vue 实例
options: RouterOptions; // 路由配置
mode: string; // 路由模式,默认 hash
history: HashHistory | HTML5History | AbstractHistory;
match: Matcher; // 一个数组,包含当前路由的所有嵌套路径片段的路由记录。?
fallback: boolean; // 当浏览器不支持 history.pushState 控制路由是否应该回退到 hash 模式。默认值为 true。
beforeHooks: Array<?NavigationGuard>; // 前置钩子集合
afterHooks: Array<?(to: Route, from: Route) => any>; // 后置钩子集合

constructor(options: RouterOptions = {}) {
this.app = null;
this.options = options;
this.beforeHooks = [];
this.afterHooks = [];
this.match = createMatcher(options.routes || []); // 匹配器

/******* 确定路由模式 - 默认为 hash *******/
let mode = options.mode || 'hash';
// 如果传入的模式为 ·history· 在浏览器环境下不支持 history 模式,则强制回退到 hash 模式
this.fallback = mode === 'history' && !supportsHistory;
if (this.fallback) {
  mode = 'hash';
}
// 在非浏览器环境下,采用 abstract 模式
if (!inBrowser) {
  mode = 'abstract';
}
this.mode = mode;

}

/**

  • 当前路由
  • @readonly
  • @type {?Route}
  • @memberof VueRouter
    */
    get currentRoute(): ?Route {
    return this.history && this.history.current;
    }

/**

  • 初始化
  • @param {Any} app Vue component instance
    */
    init(app: any) {
    // 断言有没有安装插件,如果没有抛出错误提示
    assert(
    install.installed,
    没有安装。在创建根实例之前,请确保调用 Vue.use(VueRouter)。
    );
this.app = app;
const { mode, options, fallback } = this;
// 根据不同模式实例化不同基类,无效模式下抛出错误
switch (mode) {
  case 'history':
    this.history = new HTML5History(this, options.base);
    break;
  case 'hash':
    this.history = new HashHistory(this, options.base, fallback);
    break;
  case 'abstract':
    this.history = new AbstractHistory(this);
    break;
  default:
    assert(false, `invalid mode: ${mode}`);
}

// 调用 history 属性下 listen 方法?
this.history.listen(route => {
  this.app._route = route;
});

}

/**

  • Router 实例方法 beforeEach 全局前置的导航守卫。
  • 当一个导航触发时,全局前置守卫按照创建顺序调用。
  • 守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
  • @param {Function} fn (to, from, next) => {}
  • @memberof VueRouter

*/
beforeEach(fn: Function) {
this.beforeHooks.push(fn);
}

/**

  • Router 实例方法 afterEach 全局后置钩子
  • @param {Function} fn (to, from) => {}
  • @memberof VueRouter
    */
    afterEach(fn: Function) {
    this.afterHooks.push(fn);
    }

/**

  • 编程式导航 push 导航到对应的 location
  • 这个方法会向 history 栈添加一个新的记录,
  • 所以,当用户点击浏览器后退按钮时,则回到之前的 location。
  • @param {RawLocation} location
  • @memberof VueRouter
    */
    push(location: RawLocation) {
    this.history.push(location);
    }

/**

  • 编程式导航 replace 导航到对应的 location
  • 它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
  • @param {RawLocation} location
  • @memberof VueRouter
    */
    replace(location: RawLocation) {
    this.history.replace(location);
    }

/**

  • 在 history 记录中向前或者后退多少步,类似 window.history.go(n)。
  • @param {number} n
  • @memberof VueRouter
    */
    go(n: number) {
    this.history.go(n);
    }

/**

  • 后退
  • @memberof VueRouter
    */
    back() {
    this.go(-1);
    }

/**

  • 前进
  • @memberof VueRouter
    */
    forward() {
    this.go(1);
    }

/**

  • 获取匹配到的组件列表
  • @returns {Array}
  • @memberof VueRouter
    */
    getMatchedComponents(): Array {
    if (!this.currentRoute) {
    return [];
    }
    return [].concat.apply(
    [],
    this.currentRoute.matched.map(m => {
    return Object.keys(m.components).map(key => {
    return m.components[key];
    });
    })
    );
    }
    }

// 添加 install 方法
VueRouter.install = install;

// 在浏览器环境下且 Vue 构造函数存在的情况下调用 use 方法注册插件(插件预装)
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter);
}
复制代码由上述代码实现分析,首先声明了一些属性、方法、和常用的API;接着添加 install 方法和执行 Vue.use 方法注册插件。
其中对相关代码做了详细的注释,无需再此重复论述,但其中有一些方法将在后面涉及时,做深入分析。继续分析前,这里需要先对这段代码进行解释:
this.match = createMatcher(options.routes || []); // 创建匹配器
复制代码/**

  • 创建匹配器
  • @export
  • @param {Array} routes
  • @returns {Matcher}
    */
    export function createMatcher(routes: Array): Matcher {
    const { pathMap, nameMap } = createRouteMap(routes);

function match(
raw: RawLocation,
currentRoute?: Route,
redirectedFrom?: Location
): Route {

return _createRoute(null, location);
}

function redirect(record: RouteRecord, location: Location): Route {

}

function alias(
record: RouteRecord,
location: Location,
matchAs: string
): Route {

}

function _createRoute(
record: ?RouteRecord,
location: Location,
redirectedFrom?: Location
): Route {

}

return match;
}

复制代码上述代码首先根据传入的路由配置表,创建新的映射表并从中解构出路径映射表、名称映射表,之后返回内建函数 match。这里暂时先不对其内部实现做详细介绍,之后再被调用处详细论述。
初始化
我们知道在 VueRouter -

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值