Vue-router 原理

原文:https://www.cnblogs.com/xuzhudong/p/8869699.html
前端应用的主流形式:单页应用(SPA) 大型的单页应用最显著的特色之一:采用前端路由系统,经过改变URL,在不从新请求页面的状况下,更新页面视图html

更新视图但不从新请求页面是前端路由原理的核心之一,目前唉浏览器环境中这一功能的实现主要有两种方式:前端

  • 利用URL中的hash("#")
  • 利用History interface在html5中新增的方法

h5 history api:https://developer.mozilla.org/zh-CN/docs/Web/API/Historyvue

vue-router是Vue.js框架的路由插件,下面咱们从它的源码入手,了解下vue-router 是如何经过这两种方式实现前端路由的html5

模式参数

在vue-router中是经过mode这一参数控制路由的实现模式的:vue-router

const router = new VueRouter({
    mode: 'history',
    routes: [...]
})

复制代码

vue-router的实际源码:api

export default class VueRouter {
 
 mode: string; // 传入的字符串参数,指示history类别
 history: HashHistory | HTML5History | AbstractHistory; // 实际起做用的对象属性,必须是以上三个类的枚举
 fallback: boolean; // 如浏览器不支持,'history'模式需回滚为'hash'模式
 
 constructor (options: RouterOptions = {}) {
 
 let mode = options.mode || 'hash' // 默认为'hash'模式
 this.fallback = mode === 'history' && !supportsPushState // 经过supportsPushState判断浏览器是否支持'history'模式
 if (this.fallback) {
     mode = 'hash'
 }
 if (!inBrowser) {
     mode = 'abstract' // 不在浏览器环境下运行需强制为'abstract'模式
 }
 this.mode = mode

 // 根据mode肯定history实际的类并实例化
 switch (mode) {
 case 'history':
     this.history = new HTML5History(this, options.base); breakcase 'hash':
      this.history = new HashHistory(this, options.base, this.fallback);breakcase 'abstract':
      this.history = new AbstractHistory(this, options.base); break
 default:
      if (process.env.NODE_ENV !== 'production') {
           assert(false, `invalid mode: $`)
      }
    }
 }

 init (app: any /* Vue component instance */) {
 const history = this.history;
 // 根据history的类别执行相应的初始化操做和监听
 if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
 } else if (history instanceof HashHistory) {
     const setupHashListener = () => {
     history.setupListeners()
 }
 history.transitionTo(
     history.getCurrentLocation(),
     setupHashListener,
     setupHashListener
   )
 }

 history.listen(route => {
     this.apps.forEach((app) => {
        app._route = route
   })
 })
 }

 // VueRouter类暴露的如下方法实际是调用具体history对象的方法
VueRouter.prototype.beforeEach = function beforeEach (fn) {
     return registerHook(this.beforeHooks, fn)
};

VueRouter.prototype.beforeResolve = function beforeResolve (fn) {
    return registerHook(this.resolveHooks, fn)
};

VueRouter.prototype.afterEach = function afterEach (fn) {
    return registerHook(this.afterHooks, fn)
};

VueRouter.prototype.onReady = function onReady (cb, errorCb) {
    this.history.onReady(cb, errorCb);
};

VueRouter.prototype.onError = function onError (errorCb) {
    this.history.onError(errorCb);
};

VueRouter.prototype.push = function push (location, onComplete, onAbort) {
    this.history.push(location, onComplete, onAbort);
};

VueRouter.prototype.replace = function replace (location, onComplete, onAbort) {
    this.history.replace(location, onComplete, onAbort);
};

VueRouter.prototype.go = function go (n) {
   this.history.go(n);
};

VueRouter.prototype.back = function back () {
   this.go(-1);
};

VueRouter.prototype.forward = function forward () {
    this.go(1);
};

复制代码

从上面代码能够看出:浏览器

  1. 做为参数创图的字符串属性mode只是一个标记,用来只是实际起做用的对象属性history的的实现类,二者对应关系以下: mode的对应--history:HTML5History; 'hash':HashHishtory; abstract:AbstractHistory
  2. 在初始化对应的history以前,会对mode作一些校验:若浏览器不支持HTML5History方式(经过supportPushState变量判断),则mode强制设为'abstract'
  3. VueRouter类中的onReady(), push()等方法只是一个代理,实际是调用的具体history对象的对应方法,在init()方法中初始化时,也是根据history对象具体的类别执行不一样操做

在浏览器环境下的两种方式,分别就是在HTML5History,HashHistory两个雷中实现的。History中定义的是公用和基础的方法,简单说下: ### HashHistory hash("#")符号的原本做用是加在URL中指示网页中的位置:
> http://www.example.com/index.html#print
复制代码
  • #号自己以及它后面的字符称之为hash,可经过window.location.hash属性读取。它具备如下特色:bash

    • hash虽然出如今URL中,但不会被包括在HTTP请求中。它是用来指导浏览器动做的,对服务器端彻底无用,所以,改变hash不会从新加载页面
    • 能够为hash的改变添加监听事件

    window.addEventListener("hashchange", funcRef, false)服务器

    • 每一次改变hash(window.location.hash),都会在浏览器的访问历史中增长一个记录

    因而可知,利用hash的特色,就尅来实现前端路由“更新视图但不从新请求页面”的功能。 app

    HashHistory.push()

    源码:

    HashHistory.prototype.push = function push (location, onComplete, onAbort) {
    var this$1 = this;
    
    var ref = this;
    var fromRoute = ref.current;
    this.transitionTo(location, function (route) {
      pushHash(route.fullPath);
      handleScroll(this$1.router, route, fromRoute, false);
      onComplete && onComplete(route);
    }, onAbort);
    };
    // 对window的hash进行直接赋值
    function pushHash (path) {
    if (supportsPushState) {
        pushState(getUrl(path));
    } else {
        window.location.hash = path;
    }
    }
    
    复制代码

transitionTo()方法是父类中定义用来处理路由变化中的基础逻辑的,push()方法最主要的是对window的hash进行了直接赋值

pushHash(route.fullPath)

hash的改变会自动添加到浏览器的访问历史记录中


经过Vue.mixin()方法,全局注册一个混合,影响注册以后全部建立的每一个Vue实例,该混合在beforeCreate钩子洪经过Vue.util.defineReactive()定义了响应式的_route属性。所谓响应式属性,即当_route值改变时,会自动调用Vue实例中的render()方法,更新视图。
总结一下,从**设置路由**改变到**视图更新**的流程以下:

$router.push() -->HashHistory.push() --> History.transitionTo() --> History.updateRoute() --> vm.render()