最近因为业务需要, 实现了一个简单的前端 router, 正好也来看一下 vue router 是怎么实现的. 这次先来一起看看 vue-router 初始化时做了什么.
vue router 的初始化使用步骤
我们首先来看 vue-router 的使用步骤, 然后再分别去看各个步骤都发生了什么.
使用 vue-router 需要经过一下几个步骤:
引入 vue-router:
import VueRouter from 'vue-router';
利用 vue 的插件机制, 加载 vue-router:
Vue.use(VueRouter);
实例化 VueRouter:
- const router = new VueRouter({
- routes
- })
实例化 Vue:
- const app = new Vue({
- router
- }).$mount('#app');
Vue 的插件机制
vue 提供了一个 use 方法, 来加载插件:
- Vue.use = function (plugin: Function | Object) {
- const installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
- if (installedPlugins.indexOf(plugin)> -1) {
- return this;
- }
- // additional parameters
- const args = toArray(arguments, 1);
- args.unshift(this);
- if (typeof plugin.install === 'function') {
- plugin.install.apply(plugin, args);
- } else if (typeof plugin === 'function') {
- plugin.apply(null, args);
- }
- installedPlugins.push(plugin);
- return this;
- }
该方法首先检查插件是否已经加载, 如果已经加载, 直接返回 this.
如果没有加载过, 会取所有的参数, 并将 this 放在第一个. 优先执行 plugin.install 方法, 若不能执行, 则直接执行 plugin 自身.
最后将 plugin push 到插件列表中.
那么我们就需要看 VueRouter 的 install 方法做了什么, VueRouter 类定义在 src/index.js 文件中.
利用 vue 的插件机制, 加载 vue-router
入口文件 index.js 对外 export 了一个 VueRouter 类. VueRouter 类包含了 router 的各种方法, 我们直接先来看一下 install 方法.
install 方法在 index.js 中绑定在 VueRouter 类上:
- import { install } from './install'
- VueRouter.install = install
它的实际实现是在 ./install.js 中, install 方法主要做了以下几个事情:
设置了两个 mixin:beforeCreate 和 destroyed.
- Vue.mixin({
- beforeCreate () {
- if (isDef(this.$options.router)) {
- this._routerRoot = this
- this._router = this.$options.router
- this._router.init(this)
- Vue.util.defineReactive(this, '_route', this._router.history.current)
- } else {
- this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
- }
- registerInstance(this, this)
- },
- destroyed () {
- registerInstance(this)
- }
- })
在 Vue 上绑定 $route 和 $router.
- Object.defineProperty(Vue.prototype, '$router', {
- get () { return this._routerRoot._router }
- })
- Object.defineProperty(Vue.prototype, '$route', {
- get () { return this._routerRoot._route }
- })
注册两个组件, View 和 Link.
- Vue.component('RouterView', View)
- Vue.component('RouterLink', Link)
设置 beforeRouteEnter,beforeRouteLeave 和 beforeRouteUpdate 的 merge 策略. merge 策略的介绍可以见这里 https://vuejs.org/v2/guide/mixins.html#Custom-Option-Merge-Strategies , 简单来说就是有重复的值时如何合并.
- const strats = Vue.config.optionMergeStrategies
- // use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
实例化 VueRouter
我们来看一下 VueRouter 的构造函数. 首先, constructor 会初始化一些属性:
- this.app = null
- this.apps = []
- this.options = options
- this.beforeHooks = []
- this.resolveHooks = []
- this.afterHooks = []
- this.matcher = createMatcher(options.routes || [], this)
其中 matcher 比较重要, 后面会详细说.
之后会决定使用哪种模式:
- let mode = options.mode || 'hash'
- this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
- if (this.fallback) {
- mode = 'hash'
- }
- if (!inBrowser) {
- mode = 'abstract'
- }
- this.mode = mode
- switch (mode) {
- case 'history':
- this.history = new HTML5History(this, options.base)
- break
- case 'hash':
- this.history = new HashHistory(this, options.base, this.fallback)
- break
- case 'abstract':
- this.history = new AbstractHistory(this, options.base)
- break
- default:
- if (process.env.NODE_ENV !== 'production') {
- assert(false, `invalid mode: ${mode}`)
- }
- }
由于 history 模式中的 pushstate 方法还有一些浏览器没有支持. history 模式在浏览器不支持时会回退到 hash 模式.
之后根据不同模式选择实例化不同模式的 history 类, 可以看到 hash 模式和 history 模式分别对应了 HashHistory 和 HTML5History 两个类.
此外, 如果是服务器端渲染, 需要进行 router 匹配来获取要渲染的页面. 此时服务器环境中没有 history api, 因此要自行抽象实现一个, 就是 AbstractHistory.
来源: https://juejin.im/entry/5b1e61ca5188257d7d71962a