vue-router 源码解读(第一出)

前言

VueRouter 源码其实也不多,下面就来看看它的代码实现。源码链接:https://github.com/vuejs/vue-router,版本是 vue-router v3.1.3。本文先来看看 vue-router 的几个主要的文件。

index.js 文件

/* @flow */

import { install } from './install'
import { START } from './util/route'
import { assert } 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 { HashHistory } from './history/hash'
import { HTML5History } from './history/html5'
import { AbstractHistory } from './history/abstract'
// 声明一个 Matcher 类型
import type { Matcher } from './create-matcher'

export default class VueRouter {
  static install: () => void;
  static version: string;
  // 定义基本变量
  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 = {}) {
    // 以下 this 均指向 VueRouter 的实例
    this.app = null
    this.apps = []
    // 保存配置项
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    // 传入所有路由,createMatcher 返回的是一个对象,此对象包含了 match,addRoutes 两个方法
    this.matcher = createMatcher(options.routes || [], this)
    // 取配置项中的模式,默认为 hash 模式
    let mode = options.mode || 'hash'
    // 如果 mode === 'history' 并且支持 PushState,以及 配置项中 fallback 不为false时, fallback 为真
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
    if (this.fallback) {
      // fallback 为真时设置 mode 为 hash 模式
      mode = 'hash'
    }
    // 如果不是浏览器环境,则 mode 取值 abstract,比如:node 环境
    if (!inBrowser) {
      mode = 'abstract'
    }
    // 把 mode 的值挂到实例的 mode 上
    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}`)
        }
    }
  }

  // VueRouter 上的 match 方法直接返回 this.matcher.match() 的执行结果
  match (
    raw: RawLocation,
    current?: Route,
    redirectedFrom?: Location
  ): Route {
    // 返回 this.matcher.match() 的执行结果,这个方法返回一个被冻结的(不可修改的)路由对象
    return this.matcher.match(raw, current, redirectedFrom)
  }
  // 获取当前路由
  get currentRoute (): ?Route {
    return this.history && this.history.current
  }
  // 初始化(在 install 方法中会调用此方法)
  init (app: any /* Vue 组件实例 */) {
    process.env.NODE_ENV !== 'production' && assert(
      install.installed,
      `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
      `before creating root instance.`
    )
    // 保存 Vue 实例到 apps 数组中
    this.apps.push(app)
    // https://github.com/vuejs/vue-router/issues/2639
    // 监听实例的 hook:destroyed 事件
    app.$once('hook:destroyed', () => {
      // 一旦销毁,将实例从 apps 数组中删除
      const index = this.apps.indexOf(app)
      if (index > -1) this.apps.splice(index, 1)
      // ensure we still have a main app or null if no apps
      // we do not release the router so it can be reused
      if (this.app === app) this.app = this.apps[0] || null
    })


    // 如果已存在实例,则直接返回,因为我们不需要设置新的 history 监听
    // return as we don't need to set up new history listener
    if (this.app) {
      return
    }

    // 保存当前 Vue 实例
    this.app = app
    const history = this.history
    // 根据不同的 mode 当不同的处理
    if (history instanceof HTML5History) {
      // 获取当前路由信息,后通过 transitionTo 跳转路由
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      // 如果是 Hash 模式,则监听
      const setupHashListener = () => {
        // 设置 popstate/hashchange 事件监听
        history.setupListeners()
      }
      // 跳转路由
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener, // 成功回调(onComplete)
        setupHashListener  // 失败回调(onAbort)
      )
    }
    // 添加监听回调
    history.listen(route => {
      // 循环设置每一个 app._route
      this.apps.forEach((app) => {
        app._route = route
      })
    })
  }

  // 下面这几个是路由跳转的钩子函数(beforeEach、beforeResolve、afterEach、onReady、onError)
  beforeEach (fn: Function): Function {
    return registerHook(this.beforeHooks, fn)
  }

  beforeResolve (fn: Function): Function {
    return registerHook(this.resolveHooks, fn)
  }

  afterEach (fn: Function): Function {
    return registerHook(this.afterHooks, fn)
  }

  onReady (cb: Function, errorCb?: Function) {
    this.history.onReady(cb, errorCb)
  }

  onError (errorCb: Function) {
    this.history.onError(errorCb)
  }

  // 路由跳转方法(push、replace、go、back、forward)
  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    // $flow-disable-line
    if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        this.history.push(location, resolve, reject)
      })
    } else {
      this.history.push(location, onComplete, onAbort)
    }
  }

  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    // $flow-disable-line
    if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        this.history.replace(location, resolve, reject)
      })
    } else {
      this.history.replace(location, onComplete, onAbort)
    }
  }

  go (n: number) {
    this.history.go(n)
  }

  back () {
    this.go(-1)
  }

  forward () {
    this.go(1)
  }

  // 获取匹配路由的组件
  getMatchedComponents (to?: RawLocation | Route): Array<any> {
    const route: any = to
      ? to.matched
        ? to
        : this.resolve(to).route
      : this.currentRoute
    if (!route) {
      return []
    }
    return [].concat.apply([], route.matched.map(m => {
      return Object.keys(m.components).map(key => {
        return m.components[key]
      })
    }))
  }

  resolve (
    to: RawLocation,
    current?: Route,
    append?: boolean
  ): {
    location: Location,
    route: Route,
    href: string,
    // for backwards compat
    normalizedTo: Location,
    resolved: Route
  } {
    current = current || this.history.current
    const location = normalizeLocation(
      to,
      current,
      append,
      this
    )
    const route = this.match(location, current)
    const fullPath = route.redirectedFrom || route.fullPath
    const base = this.history.base
    const href = createHref(base, fullPath, this.mode)
    return {
      location,
      route,
      href,
      // for backwards compat
      normalizedTo: location,
      resolved: route
    }
  }

  // 外部动态添加路由的方法
  addRoutes (routes: Array<RouteConfig>) {
    this.matcher.addRoutes(routes)
    if (this.history.current !== START) {
      this.history.transitionTo(this.history.getCurrentLocation())
    }
  }
}

// 注册钩子函数(往 list 数组里塞回调函数)
function registerHook (list: Array<any>, fn: Function): Function {
  list.push(fn)
  // 返回一个删除当前回调的匿名函数
  return () => {
    const i = list.indexOf(fn)
    if (i > -1) list.splice(i, 1)
  }
}

// 创建 url
function createHref (base: string, fullPath: string, mode) {
  var path = mode === 'hash' ? '#' + fullPath : fullPath
  return base ? cleanPath(base + '/' + path) : path
}

// VueRouter 对象添加 install 方法
VueRouter.install = install
VueRouter.version = '__VERSION__'

// 如果是浏览器环境自动调用 Vue.use() 方法
if (inBrowser && window.Vue) {
  window.Vue.use(VueRouter)
}

这个文件的代码不多,主要还是其调用的一些方法。主要有:

  1. 通过 createMatcher() 方法获取 matcher 对象
  2. 根据配置及环境设置 mode
  3. 通过 transitionTo() 方法进行路由跳转

其中的 init() 方法是在 install() 函数中被调用。

create-matcher.js 文件( createMatcher() 方法)

/* @flow */

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'

export type Matcher = {
  match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
  addRoutes: (routes: Array<RouteConfig>) => void;
};

export function createMatcher (
  routes: Array<RouteConfig>,
  router: VueRouter
): Matcher {
  // 创建路由映射表,并从对象中提取出 pathList(保存路由的数组), pathMap(路由对应路由的映射表), nameMap(名称对应路由的映射表)
  const { pathList, pathMap, nameMap } = createRouteMap(routes)
  // 添加路由
  function addRoutes (routes) {
    createRouteMap(routes, pathList, pathMap, nameMap)
  }

  // 返回一个匹配的路由对象
  function match (
    raw: RawLocation,
    currentRoute?: Route,
    redirectedFrom?: Location
  ): Route {
    // 路径处理
    const location = normalizeLocation(raw, currentRoute, false, router)
    // 提取 name
    const { name } = location
    // name 存在
    if (name) {
      // 根据 name 从 nameMap 对象中取出对应的 record (路由)
      const record = nameMap[name]
      if (process.env.NODE_ENV !== 'production') {
        warn(record, `Route with name '${name}' does not exist`)
      }
      // 如果 record 不存在,创建并返回路由
      if (!record) return _createRoute(null, location)
      const paramNames = record.regex.keys
        .filter(key => !key.optional)
        .map(key => key.name)
      // 如果 location.params 不为对象,则赋值空对象
      if (typeof location.params !== 'object') {
        location.params = {}
      }
      // 当前路由对象存在,并且此对象上的 params 为对象的话
      if (currentRoute && typeof currentRoute.params === 'object') {
        // 遍历
        for (const key in currentRoute.params) {
          // 如果 key 不存在 location.params 但存在于 paramNames
          if (!(key in location.params) && paramNames.indexOf(key) > -1) {
            // 把 currentRoute.params[key] 赋值给 location.params[key]
            location.params[key] = currentRoute.params[key]
          }
        }
      }
      // 通过 fillParams() 方法拼接 url 并赋值给 location.path,也就是说 location.path 这个值已经是一完整的 url 字符串(路径+参数)
      location.path = fillParams(record.path, location.params, `named route "${name}"`)
      // 返回路由对象
      return _createRoute(record, location, redirectedFrom)
    } else if (location.path) { // 如果 location.path 存在
      location.params = {}
      // 遍历 path 数组
      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)
        }
      }
    }
    // 如果前面都没有匹配到
    return _createRoute(null, location)
  }
  // 返回一个匹配的路由对象
  function redirect (
    record: RouteRecord,
    location: Location
  ): Route {
    // 缓存 record.redirect 的值
    const originalRedirect = record.redirect
    // 如果 originalRedirect 是一个函数,则向此函数传入创建的路由对象并执行它
    let redirect = typeof originalRedirect === 'function'
      ? originalRedirect(createRoute(record, location, null, router))
      : originalRedirect
    // 如果 redirect 是一个字符串
    if (typeof redirect === 'string') {
      // 则把它改成对象的形式
      redirect = { path: redirect }
    }
    // 如果 redirect 没有值,或者类型不为 对象
    if (!redirect || typeof redirect !== 'object') {
      if (process.env.NODE_ENV !== 'production') {
        warn(
          false, `invalid redirect option: ${JSON.stringify(redirect)}`
        )
      }
      return _createRoute(null, location)
    }

    // 把 redirect 赋值给 re
    const re: Object = redirect
    // 从 rd 中提取 name 和 path 属性
    const { name, path } = re
    // 从 location 中提取 query、hash 和 params 属性
    let { query, hash, params } = location
    // query、hash 和 params 最终取值(三个属性如果有存在 re 中的就取 re 上的,否则取 location 上的)
    query = re.hasOwnProperty('query') ? re.query : query
    hash = re.hasOwnProperty('hash') ? re.hash : hash
    params = re.hasOwnProperty('params') ? re.params : params
    // 如果 re 有 name
    if (name) {
      // 从 映射表中获取目标路由记录
      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) { // 如果 path 存在
      // 1. resolve relative redirect
      const rawPath = resolveRecordPath(path, record)
      // 拼接完整的 url
      const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
      return match({
        _normalized: true,
        path: resolvedPath,
        query,
        hash
      }, undefined, location)
    } else { // 如果 name 和 path 都不存在
      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 {
    // 拼接 url 字符串
    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 赋值
      location.params = aliasedMatch.params
      return _createRoute(aliasedRecord, location)
    }
    return _createRoute(null, location)
  }

  function _createRoute (
    record: ?RouteRecord,
    location: Location,
    redirectedFrom?: Location
  ): Route {
    // 如果 record 和record.redirect 同时存在(优先)
    if (record && record.redirect) {
      return redirect(record, redirectedFrom || location)
    }
    // 如果 record 和record.matchAs 同时存在
    if (record && record.matchAs) {
      return alias(record, location, record.matchAs)
    }
    // 如果 redirect 和 matchAs 都不存在,创建并返回路由对象
    return createRoute(record, location, redirectedFrom, router)
  }

  return {
    match,
    addRoutes
  }
}

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

  if (!m) {
    return false
  } else if (!params) {
    // m 存在,params 不存在返回true
    return true
  }

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

  return true
}

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

这个方法返回一个包含 match 和 addRouters 属性的对象。而其中通过 createRouteMap() 方法路由映射表。

create-route-map.js(createRouteMap() 方法)

/* @flow */


import Regexp from 'path-to-regexp'
import { cleanPath } from './util/path'
import { assert, warn } from './util/warn'


// 返回一个含有 pathList、pathMap、nameMap 的对象
// 所以这个函数的作用就是处理三种数据并返回
export function createRouteMap (
  routes: Array<RouteConfig>,
  oldPathList?: Array<string>,
  oldPathMap?: Dictionary<RouteRecord>,
  oldNameMap?: Dictionary<RouteRecord>
): {
  pathList: Array<string>,
  pathMap: Dictionary<RouteRecord>,
  nameMap: Dictionary<RouteRecord>
} {
  // 下面三个变量有传则取传的参数值,否则取默认
  const pathList: Array<string> = oldPathList || []
  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
  const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)


  routes.forEach(route => {
    addRouteRecord(pathList, pathMap, nameMap, route)
  })


  // 确保通配符总是在最后
  for (let i = 0, l = pathList.length; i < l; i++) {
    if (pathList[i] === '*') {
      // 将 '*' 从数组中删除,并把删除的项放到 pathList 的最后面
      pathList.push(pathList.splice(i, 1)[0])
      l--
      i--
    }
  }


  if (process.env.NODE_ENV === 'development') {
    // 找出第一个字符不是 '*' 或者 '/' 的路径
    const found = pathList
      .filter(path => path && path.charAt(0) !== '*' && path.charAt(0) !== '/')
    // 如果存在则逐一警告提示
    if (found.length > 0) {
      const pathNames = found.map(path => `- ${path}`).join('\n')
      warn(false, `Non-nested routes must include a leading slash character. Fix the following routes: \n${pathNames}`)
    }
  }


  return {
    pathList,
    pathMap,
    nameMap
  }
}


function addRouteRecord (
  pathList: Array<string>,
  pathMap: Dictionary<RouteRecord>,
  nameMap: Dictionary<RouteRecord>,
  route: RouteConfig,
  parent?: RouteRecord,
  matchAs?: string
) {
  // 从 route 中提取 path 和 name 属性
  const { path, name } = route
  if (process.env.NODE_ENV !== 'production') {
    assert(path != null, `"path" is required in a route configuration.`)
    assert(
      typeof route.component !== 'string',
      `route config "component" for path: ${String(
        path || name
      )} cannot be a ` + `string id. Use an actual component instead.`
    )
  }
  // 如果路由中没有配置 pathToRegexpOptions 则赋值空对象
  const pathToRegexpOptions: PathToRegexpOptions =
    route.pathToRegexpOptions || {}
  const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict)
  // 匹配规则是否大小写敏感
  if (typeof route.caseSensitive === 'boolean') {
    pathToRegexpOptions.sensitive = route.caseSensitive
  }


  const record: RouteRecord = {
    path: normalizedPath,
    regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
    components: route.components || { default: route.component },
    instances: {},
    name,
    parent,
    matchAs,
    redirect: route.redirect,
    beforeEnter: route.beforeEnter,
    meta: route.meta || {},
    props:
      route.props == null
        ? {}
        : route.components
          ? route.props
          : { default: route.props }
  }
  // 如果存在子路由
  if (route.children) {
    // Warn if route is named, does not redirect and has a default child route.
    // If users navigate to this route by name, the default child will
    // not be rendered (GH Issue #629)
    if (process.env.NODE_ENV !== 'production') {
      if (
        route.name &&
        !route.redirect &&
        route.children.some(child => /^\/?$/.test(child.path))
      ) {
        warn(
          false,
          `Named Route '${route.name}' has a default child route. ` +
            `When navigating to this named route (:to="{name: '${
              route.name
            }'"), ` +
            `the default child route will not be rendered. Remove the name from ` +
            `this route and use the name of the default child route for named ` +
            `links instead.`
        )
      }
    }
    route.children.forEach(child => {
      const childMatchAs = matchAs
        ? cleanPath(`${matchAs}/${child.path}`)
        : undefined
        // 把子路由添加到路由表记录中(递归)
      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
    })
  }


  // 如果路由不存在于 pathMap 对象中,则把些 path 保存到 pathList 以及 pathMap 对象中
  if (!pathMap[record.path]) {
    pathList.push(record.path)
    pathMap[record.path] = record
  }


  if (route.alias !== undefined) {
    // 把别名作为一个数组保存
    const aliases = Array.isArray(route.alias) ? route.alias : [route.alias]
    for (let i = 0; i < aliases.length; ++i) {
      const alias = aliases[i]
      if (process.env.NODE_ENV !== 'production' && alias === path) {
        warn(
          false,
          `Found an alias with the same value as the path: "${path}". You have to remove that alias. It will be ignored in development.`
        )
        // skip in dev to make it work
        continue
      }


      const aliasRoute = {
        path: alias,
        children: route.children
      }
      // 把别名路由也添加路由记录
      addRouteRecord(
        pathList,
        pathMap,
        nameMap,
        aliasRoute,
        parent,
        record.path || '/' // matchAs
      )
    }
  }


 // 生成名字与路由的映射表
  if (name) {
    if (!nameMap[name]) {
      nameMap[name] = record
    } else if (process.env.NODE_ENV !== 'production' && !matchAs) {
      warn(
        false,
        `Duplicate named routes definition: ` +
          `{ name: "${name}", path: "${record.path}" }`
      )
    }
  }
}


// 生成 path 对应的正则
function compileRouteRegex (
  path: string,
  pathToRegexpOptions: PathToRegexpOptions
): RouteRegExp {
  // 这个方法主要是这一行代码
  // Regexp 为 path-to-regexp 的引用,这个方法处理完后会返回一个 path 对应的正则
  const regex = Regexp(path, [], pathToRegexpOptions)
  if (process.env.NODE_ENV !== 'production') {
    // keys 对象用于判断 key 重复
    const keys: any = Object.create(null)
    // 检查 regex.keys 中是否含有同名 name 对象
    // regex 的 keys 属性从何而来???
    regex.keys.forEach(key => {
      warn(
        !keys[key.name],
        `Duplicate param keys in route with path: "${path}"`
      )
      keys[key.name] = true
    })
  }
  return regex
}


// 处理 path 路径
function normalizePath (
  path: string, // 当前路径
  parent?: RouteRecord, // 父路由记录
  strict?: boolean
): string {
  if (!strict) path = path.replace(/\/$/, '')
  // 如果 path 中第一个字符是 "/" 说明是顶级路径,直接返回 path
  if (path[0] === '/') return path
  // 或者如果 parent 为 null 也直接返回 path
  if (parent == null) return path
  // 如果以前条件都不满足,那么返回 cleanPath() 方法的处理结果
  return cleanPath(`${parent.path}/${path}`)
}


//  pathList、pathMap、nameMap 这三个数组/对象是保存了所有路由的相关信息(包括路由别名的对应关系,及子路由的对应关系)

createRouteMap() 方法主要是处理路径以及名称/路由与路由的对应关系,并分别保存在 pathList,,pathMap,nameMap 这三个变量中。

install.js 文件

import View from './components/view'
import Link from './components/link'

export let _Vue

export function install(Vue) {
  // 如果已经安装,直接返回
  if (install.installed && _Vue === Vue) return
  // 设置插件已安装
  install.installed = true
  // 缓存实例
  _Vue = Vue
  // undefined 判断
  const isDef = v => v !== undefined

  const registerInstance = (vm, callVal) => {
    let i = vm.$options._parentVnode
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }

  // 混入钩子函数
  Vue.mixin({
    beforeCreate() {
      // 注意:这里的 this 指的是 Vue 实例
      if (isDef(this.$options.router)) {
        // 把 Vue 实例挂载到 Vue 实例的 _routerRoot 上
        this._routerRoot = this
        // 把 router 挂载到 Vue 实例的 _router 上
        this._router = this.$options.router
        // 调用 router 实例的 init 方法, 初始化路由
        this._router.init(this)
        // 设置响应的 _route
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        // 保证 this._routerRoot 有值
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this, this)
    },
    destroyed() {
      registerInstance(this)
    }
  })
  // 把路由对象挂载到 Vue 的原型上
  Object.defineProperty(Vue.prototype, '$router', {
    get() {
      return this._routerRoot._router
    }
  })
  // 把当前路由对象挂载到 Vue 的原型上
  Object.defineProperty(Vue.prototype, '$route', {
    get() {
      return this._routerRoot._route
    }
  })
  // 全局注册 RouterView 和 RouterLink
  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)

  const strats = Vue.config.optionMergeStrategies
  // 对路由钩子使用相同的合并策略
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}

install() 方法就更简单了。

  1. 主要是向 beforeCreate 钩子函数中混入初始化路由的逻辑
  2. 将路由对象和当前路由对象都挂载到 Vue 的原型上,让所有的 Vue 实例都可以访问到
  3. 全局注册 RouterView 和 RouterLink 组件
声明

1.原创文章,不经本站同意,不得以任何形式转载,如有不便,请多多包涵!

2.本文永久链接:http://yunkus.com/post/5dbbeb572bb85f85

3.如果觉得本文对你有帮助,或者解决了你的问题,不妨扫一扫右边的二维码打赏支持,你的一分一毫,可能会让世界变得更美好。

微信
扫一扫,赏我
支付宝
扫一扫,赏我