vue-router源码分析(中)

简介

路由的概念相信大部分同学并不陌生,我们在用 Vue 开发过实际项目的时候都会用到 Vue-Router 这个官方插件来帮我们解决路由的问题。它的作用就是根据不同的路径映射到不同的视图。本文不再讲述路由的基础使用和API,不清楚的同学可以自行查阅官方文档vue-router3 对应 vue2vue-router4 对应 vue3。今天我们从源码出发以vue-router 3.5.3源码为例,一起来分析下Vue-Router的具体实现。

由于篇幅原因,vue-router源码分析分上、中、下三篇文章讲解。

vue-router源码分析(上)

vue-router源码分析(中)

vue-router源码分析(下)

在上文中我者讲述路路由的一些前置知识,以及路由安装模块的分析。今天我们来讲讲路由的实例化到底都干了些什么事情。

实例化

实例化就是我们new VueRouter({routes})的过程,我们来重点分析下VueRouter的构造函数。

分析VueRouter构造函数

VueRouter的构造函数在src/index.js中。

// index.js

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 = []
  this.matcher = createMatcher(options.routes || [], this)

  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}`)
      }
  }
} 
构造函数参数

构造函数接收RouterOptions参数,也就是我们new VueRouter({})传递的参数。

具体参数意思如下:

export interface RouterOptions {
  routes?: RouteConfig[] // 路由配置规则列表
  mode?: RouterMode // 模式
  fallback?: boolean // 是否启用回退到hash模式
  base?: string // 路由base url
  linkActiveClass?: string // router-link激活时类名
  linkExactActiveClass?: string // router-link精准激活时类名
  parseQuery?: (query: string) => Object // 自定义解析qs的方法
  stringifyQuery?: (query: Object) => string // 自定义序列化qs的方法
  scrollBehavior?: ( // 控制滚动行为
    to: Route,
    from: Route,
    savedPosition: Position | void ) => PositionResult | Promise<PositionResult> | undefined | null
} 
参数初始化

我们看到在最开始有些参数的初始化,这些参数到底是什么呢?

this.app 用来保存根 Vue 实例。

this.apps 用来保存持有 $options.router 属性的 Vue 实例。

this.options 保存传入的路由配置。

this.beforeHooksthis.resolveHooksthis.afterHooks 表示一些钩子函数,我们之后会介绍。

this.fallback 表示在浏览器不支持 history.pushState 的情况下,根据传入的 fallback 配置参数,决定是否回退到hash模式。

this.mode 表示路由创建的模式。

创建matcher

通过createMatcher(options.routes || [], this)生成matcher,这个matcher对象就是前面聊的匹配器,负责url匹配,它接收了routesrouter实例

这个非常重要,我们来重点分析。

分析create-matcher
// src/create-matcher.js

...

// Matcher数据结构,包含四个方法
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>;
};

// createMatcher返回Matcher对象
export function createMatcher ( routes: Array<RouteConfig>, // 路由配置列表
  router: VueRouter // VueRouter实例 ): Matcher {

  // 创建路由映射表
  const { pathList, pathMap, nameMap } = createRouteMap(routes)

  // 批量添加路由
  function addRoutes (routes) {
    // 所以这里会重新调用createRouteMap方法
    createRouteMap(routes, pathList, pathMap, nameMap)
  }
  
  // 添加单个路由
  function addRoute (parentOrRoute, route) {
    ...
  }

  // 获取路由关系数组
  function getRoutes () {
    return pathList.map(path => pathMap[path])
  }
  
  // 传入Location和Route,返回匹配的Route对象
  function match ( raw: RawLocation,
    currentRoute?: Route,
    redirectedFrom?: Location ): Route {
    ...
  }

  return {
    match,
    addRoute,
    getRoutes,
    addRoutes
  }
  
  ...
} 

matcher对象不光定义了match、addRoute、addRoutes、getRoutes四个方法供我们调用,而且在最开始通过createRouteMap()方法创建了路由映射表RouteMap

这里的match方法非常重要,后面我们会分析。

下面我们来看看createRouteMap()方法。

分析createRouteMap
// src/create-route-map.js

// 创建路由映射map、添加路由记录
export function createRouteMap ( routes: Array<RouteConfig>, // 路由配置列表
  oldPathList?: Array<string>, // 旧pathList
  oldPathMap?: Dictionary<RouteRecord>, // 旧pathMap
  oldNameMap?: Dictionary<RouteRecord>// 旧nameMap ): {
  pathList: Array<string>,
  pathMap: Dictionary<RouteRecord>,
  nameMap: Dictionary<RouteRecord>
} {

  // 若旧的路由相关映射列表及map存在,则使用旧的初始化(借此实现添加路由功能)
  // the path list is used to control path matching priority
  const pathList: Array<string> = oldPathList || []
  // $flow-disable-line
  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
  // $flow-disable-line
  const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)
  
  // 遍历路由配置对象,生成/添加路由记录
  routes.forEach(route => {
    addRouteRecord(pathList, pathMap, nameMap, route)
  })
  
  // 确保path:*永远在在最后
  for (let i = 0, l = pathList.length; i < l; i++) {
    if (pathList[i] === '*') {
      pathList.push(pathList.splice(i, 1)[0])
      l--
      i--
    }
  }
  
  // 开发环境,提示非嵌套路由的path必须以/或者*开头
  if (process.env.NODE_ENV === 'development') {
    // warn if routes do not include leading slashes
    const found = pathList
    // check for missing leading slash
      .
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值