vue-router源码解析

vue-router简介

vue-router工作原理:

  1. url改变
  2. 触发监听事件 (原理见路由模式)
  3. 改变vue-router里面的current变量
  4. vue监听current的监听者
  5. 获取到新的组件
  6. render新组件

vue-router如何实现无刷新页面切换:

  1. 采用某种方式使url发生改变。这种方式可能是调用HTML5 history API实现,也可能是点击前进后退或者改变路由hash.但是不管采用哪种方式,它都不能造成浏览器刷新,仅仅只是单纯的url发生变化.
  • history.pushState(state,title,url) :
    无刷新的向当前history插入一条历史状态,但是并不会造成页面的重新加载和浏览器向服务器发送请求
  • window.onpopState() :
    当点击后退,前进按钮,或调用history.go()方法时,该方法会被触发,但是history.pushState并不会直接触发popstate.
    回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前 URL 所提供的状态对象(即这两个方法的第一个参数)这个state对象也可以直接通过history对象读取
window.onpopstate = function (event) {//
  console.log('location: ' + document.location);
  console.log('state: ' + JSON.stringify(event.state));
};
  1. 监听到url的变化之后,根据不同的路径获取渲染内容,再把内容填充到div容器里.从上面案例可知,监听url的变化一般在两个地方,第一是在window.onpopstate包裹的回调函数里,第二是在执行history.pushState或history.replaceState的后面. render函数根据跳转路径的不同动态改变app容器里面的内容,从而便模拟出了点击不同路径页面似乎发生了跳转的效果.
  • render:function(createElement){return createElement(APP)} 其中的形参是一个方法,作用是根据给定的组件渲染,把给定的组件渲染到el区域中

路由模式

  • hash模式
    哈希路径中带个#,#后面的就是hash的内容;
    可以通过location.hash拿到
    可以通过onhashchange监听hash的改变

  • history模式
    正常路径,没有#
    可以通过location.pathname拿到
    可以通过onpopstate监听history的改变

深入源码

例子

先来看看在vue中怎么用vue-router
import vue和vue-router后,
用vue.use(vueRouter)来注册组件
而这个vue.use函数又会执行vue-router.install函数

import Vue from 'vue'
import VueRouter from '../c-router' // 引用
import home from '../views/Home.vue'

Vue.use(VueRouter)//1 注册插件
//作用
//1. 执行里面方法
//2. 如果这个方法有一个属性install 并且这个属性是一个方法, Vue.use就会执行install方法
// 如果没有install方法, 就会执行这个父级方法(vuerouter)本身
//3. install这个方法的第一参数是vue(就是vue的构造函数)

const routes = [
	{
		path: '',
		name: 'Layout',
		children: [
			{
				path: '/home',
				name: 'Home',
				component: Home
			},
			{
				path: '/about',
				name: 'About',
				component: () => import('../viewa/About.vue')
			}
		]
	}
]

const router = new VueRouter({
	mode:'hash',
	routes
})

vue中怎么注册vue-router

再来看看源码

先来看看vue2源码中的initUse函数,其中声明了Vue.use函数
路径:src/core/global-api/use.ts

import type { GlobalAPI } from 'types/global-api'
import { toArray, isFunction } from '../util/index'

export function initUse(Vue: GlobalAPI) {
//注意看这里的Vue.use, 就是 之前vue中怎么调用vue-router 小节中,我们用到的Vue.use(VueRouter)
  Vue.use = function (plugin: Function | any) {//plugin:插件
	//重复注册插件的情况:
    const installedPlugins = this._installedPlugins || (this._installedPlugins = [])
    if (installedPlugins.indexOf(plugin) > -1) {
      return this//若已经注册过(用indexof能找到),直接返回
    }

    // additional parameters
    const args = toArray(arguments, 1)//args就是之前vue中怎么调用vue-router 小节中的install的参数
    args.unshift(this) //this就是vue的实例
    if (isFunction(plugin.install)) {//如果plugin的install属性是方法的话
      plugin.install.apply(plugin, args)//调用
    } else if (isFunction(plugin)) {//若无
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

vue-router中install方法实现 https://github.com/vuejs/vue-router/tree/dev
路径:dist/vue-router.js

  function install (Vue) {
  //判断是否已经安装过插件
    if (install.installed && _Vue === Vue) { return }
    install.installed = true;

    _Vue = Vue;
//辅助函数,判断一个值是否已定义
    var isDef = function (v) { return v !== undefined; };
//注册路由实例的辅助函数
    var registerInstance = function (vm, callVal) {
      var i = vm.$options._parentVnode;
      if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
        i(vm, callVal);
      }
    };
    
//全局混入,在组件的beforeCreate和destroyed生命狗子里执行一些操作
    Vue.mixin({
      beforeCreate: function beforeCreate () {
        if (isDef(this.$options.router)) {//如果组件定义了$options.router,则表示当前组件是根组件
          this._routerRoot = this;
          this._router = this.$options.router;
          this._router.init(this);//初始化路由
          Vue.util.defineReactive(this, '_route', this._router.history.current);//定义响应式的_route属性,有了这个响应式的路由对象,就可以在路由更新的时候及时的通知RouterView去更新组件了
        } else {//如果不是根组件,则将_routerRoot指向最近的父级根组件
          this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
        }
        registerInstance(this, this);// 注册路由实例
      },
      destroyed: function destroyed () {
        registerInstance(this);// 销毁时注销路由实例
      }
    });
    
// 在Vue原型上定义$router属性的访问器
    Object.defineProperty(Vue.prototype, '$router', {
      get: function get () { return this._routerRoot._router }
    });

    Object.defineProperty(Vue.prototype, '$route', {
      get: function get () { return this._routerRoot._route }
    });

    Vue.component('RouterView', View);// 注册RouterView组件
    Vue.component('RouterLink', Link);// 注册RouterLink组件

    var strats = Vue.config.optionMergeStrategies;
    // use the same hook merging strategy for route hooks
    strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
  }

至此,Vue.use(VueRouter)//1 注册插件 这一步中的相关源码都看完了,我们继续往下

VueRouter类的实例化

来看看vueRouter是怎么实例化的,
路径:src/router.js

import { install } from './install'
import { START } from './util/route'
import { assert, warn } from './util/warn'
import { inBrowser } from './util/dom'
import { cleanPath } from './util/path'
import { createMatcher } from './create-matcher'
import { normalizeLocation } from './util/location'
import { supportsPushState } from './util/push-state'
import { handleScroll } from './util/scroll'
import { isNavigationFailure, NavigationFailureType } from './util/errors'
import { HashHistory } from './history/hash'
import { HTML5History } from './history/html5'
import { AbstractHistory } from './history/abstract'
import type { Matcher } from './create-matcher'

export default class VueRouter {
  static install: () => void
  static version: string
  static isNavigationFailure: Function
  static NavigationFailureType: any
  static START_LOCATION: Route

  app: any
  apps: Array<any>
  ready: boolean
  readyCbs: Array<Function>
  options: RouterOptions
  mode: string
  history: HashHistory | HTML5History | AbstractHistory
  matcher: Matcher
  fallback: boolean
  beforeHooks: Array<?NavigationGuard>
  resolveHooks: Array<?NavigationGuard>
  afterHooks: Array<?AfterNavigationHook>

  constructor (options: RouterOptions = {}) {
    if (process.env.NODE_ENV !== 'production') {
      warn(this instanceof VueRouter, `Router must be called with the new operator.`)
    }
    this.app = null
    this.apps = []
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    // 创建路由匹配实例;传人我们定义的routes:包含path和component的对象;以防你们忘了是啥,下面是一个新建router的例子
    // const router = new VueRouter({
    //	  mode: 'history',
	//    routes: [{  path: '/',
	//                component: Main, }],
	//});
    this.matcher = createMatcher(options.routes || [], this)

    let mode = options.mode || 'hash'//判断模式是哈希还是history
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false // 判断浏览器是否支持history,如果不支持则回退到hash模式;
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      mode = 'abstract'
    }
    this.mode = mode

    switch (mode) {// 根据不同模式创建对应的history实例
      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}`)
        }
    }
  }

来看看createMatcher方法
路径:src/create-matcher.js

import type VueRouter from './index'
import { resolvePath } from './util/path'
import { assert, warn } from './util/warn'
import { createRoute } from './util/route'
import { fillParams } from './util/params'
import { createRouteMap } from './create-route-map'
import { normalizeLocation } from './util/location'
import { decode } from './util/query'

export type Matcher = {
  match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
  addRoutes: (routes: Array<RouteConfig>) => void;
  addRoute: (parentNameOrRoute: string | RouteConfig, route?: RouteConfig) => void;
  getRoutes: () => Array<RouteRecord>;
};

// routes为我们初始化VueRouter的路由配置;router就是我们的VueRouter实例;
export function createMatcher (
  routes: Array<RouteConfig>,
  router: VueRouter
): Matcher {
  const { pathList, pathMap, nameMap } = createRouteMap(routes)//根据新的routes生成路由;pathList是根据routes生成的path数组;pathMap是根据path的名称生成的map;如果我们在路由配置上定义了name,那么就会有这么一个name的Map;

//添加路由
  function addRoutes (routes) {
    createRouteMap(routes, pathList, pathMap, nameMap)
  }

  function addRoute (parentOrRoute, route) {
    const parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined
    // $flow-disable-line
    createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent)

    // add aliases of parent
    if (parent && parent.alias.length) {
      createRouteMap(
        // $flow-disable-line route is defined if parent is
        parent.alias.map(alias => ({ path: alias, children: [route] })),
        pathList,
        pathMap,
        nameMap,
        parent
      )
    }
  }

  function getRoutes () {
    return pathList.map(path => pathMap[path])
  }

//路由匹配函数,根据给定的路由地址信息进行匹配,并返回匹配到的路由对象
  function match (
    raw: RawLocation,// 原始的路由地址信息
    currentRoute?: Route, // 当前路由对象
    redirectedFrom?: Location// 重定向来源的路由地址信息
  ): Route {
    const location = normalizeLocation(raw, currentRoute, false, router)
    const { name } = location

    if (name) { //如果有路由名称
      const record = nameMap[name]// 根据路由名称查找对应的记录
      if (process.env.NODE_ENV !== 'production') {
        warn(record, `Route with name '${name}' does not exist`)
      }
      if (!record) return _createRoute(null, location)
      const paramNames = record.regex.keys// 获取路由参数名称
        .filter(key => !key.optional)
        .map(key => key.name)

      if (typeof location.params !== 'object') {
        location.params = {}
      }
// 处理当前路由的参数
      if (currentRoute && typeof currentRoute.params === 'object') {
        for (const key in currentRoute.params) {
          if (!(key in location.params) && paramNames.indexOf(key) > -1) {
            location.params[key] = currentRoute.params[key]
          }
        }
      }
// 填充路由参数到路由路径中
      location.path = fillParams(record.path, location.params, `named route "${name}"`)
      return _createRoute(record, location, redirectedFrom)// 创建匹配的路由对象并返回
    } else if (location.path) { // 如果没有路由名称但有路由路径
      location.params = {}
      for (let i = 0; i < pathList.length; i++) {
        const path = pathList[i]
        const record = pathMap[path]
        if (matchRoute(record.regex, location.path, location.params)) {
          return _createRoute(record, location, redirectedFrom)
        }
      }
    }
    // no match 没有匹配的路由,创建一个空的路由对象
    return _createRoute(null, location)
  }

  function redirect (
    record: RouteRecord,
    location: Location
  ): Route {
    const originalRedirect = record.redirect
    let redirect = typeof originalRedirect === 'function'
      ? originalRedirect(createRoute(record, location, null, router))
      : originalRedirect

    if (typeof redirect === 'string') {
      redirect = { path: redirect }
    }

    if (!redirect || typeof redirect !== 'object') {
      if (process.env.NODE_ENV !== 'production') {
        warn(
          false, `invalid redirect option: ${JSON.stringify(redirect)}`
        )
      }
      return _createRoute(null, location)
    }

    const re: Object = redirect
    const { name, path } = re
    let { query, hash, params } = location
    query = re.hasOwnProperty('query') ? re.query : query
    hash = re.hasOwnProperty('hash') ? re.hash : hash
    params = re.hasOwnProperty('params') ? re.params : params

    if (name) {
      // resolved named direct
      const targetRecord = nameMap[name]
      if (process.env.NODE_ENV !== 'production') {
        assert(targetRecord, `redirect failed: named route "${name}" not found.`)
      }
      return match({
        _normalized: true,
        name,
        query,
        hash,
        params
      }, undefined, location)
    } else if (path) {
      // 1. resolve relative redirect
      const rawPath = resolveRecordPath(path, record)
      // 2. resolve params
      const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
      // 3. rematch with existing query and hash
      return match({
        _normalized: true,
        path: resolvedPath,
        query,
        hash
      }, undefined, location)
    } else {
      if (process.env.NODE_ENV !== 'production') {
        warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)
      }
      return _createRoute(null, location)
    }
  }

  function alias (
    record: RouteRecord,
    location: Location,
    matchAs: string
  ): Route {
    const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`)
    const aliasedMatch = match({
      _normalized: true,
      path: aliasedPath
    })
    if (aliasedMatch) {
      const matched = aliasedMatch.matched
      const aliasedRecord = matched[matched.length - 1]
      location.params = aliasedMatch.params
      return _createRoute(aliasedRecord, location)
    }
    return _createRoute(null, location)
  }

//根据给定的路由记录(record)、路由地址信息(location)和重定向来源(redirectedFrom)来创建一个路由对象(Route)
  function _createRoute (
    record: ?RouteRecord,
    location: Location,
    redirectedFrom?: Location
  ): Route {
    if (record && record.redirect) {
      return redirect(record, redirectedFrom || location)
    }
    if (record && record.matchAs) {
      return alias(record, location, record.matchAs)
    }
    return createRoute(record, location, redirectedFrom, router)
  }

  return {
    match,
    addRoute,
    getRoutes,
    addRoutes
  }
}

function matchRoute (
  regex: RouteRegExp,
  path: string,
  params: Object
): boolean {
  const m = path.match(regex)

  if (!m) {
    return false
  } else if (!params) {
    return true
  }

  for (let i = 1, len = m.length; i < len; ++i) {
    const key = regex.keys[i - 1]
    if (key) {
      // Fix #1994: using * with props: true generates a param named 0
      params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i]
    }
  }

  return true
}

function resolveRecordPath (path: string, record: RouteRecord): string {
  return resolvePath(path, record.parent ? record.parent.path : '/', true)
}

后面的读不动了 有空继续

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值