vueRouter简记(上)

简介: 前言最近抽时间学习了vueRouter源码,基本也就是走马观花式地看了一遍。虽然很多细节和原理没有去深入分析,但还是想通过博客记录下自己从中学习到的点滴。

前言


最近抽时间学习了vueRouter源码,基本也就是走马观花式地看了一遍。虽然很多细节和原理没有去深入分析,但还是想通过博客记录下自己从中学习到的点滴。


目录结构


相对于vue源码来说,router源码的目录结构简单许多


src目录
│  create-matcher.js   // matcher相关,主要用于pathList的生成及匹配
│  create-route-map.js   // route相关,将path创建为route对象
│  index.js   // vueRouter实例相关
│  install.js   // 插件安装
├─components 
│      link.js   // router-link组件定义
│      view.js   // router-view组件定义
├─history   // history用于跳转相关处理
│      abstract.js
│      base.js   // history基本类其它三个继承自base,分别对应三种不同的路由模式
│      hash.js
│      html5.js
└─util   // 辅助函数
        async.js
        dom.js
        errors.js
        location.js
        misc.js
        params.js
        path.js
        push-state.js
        query.js
        resolve-components.js
        route.js
        scroll.js
        state-key.js
        warn.js
复制代码


插件安装


我们知道vue插件是通过执行install传入Vue来执行install方法进行初始化逻辑的,vueRouter也不例外。我们看看其中几个重要的逻辑。


// install
Object.defineProperty(Vue.prototype, '$router', {
  get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
  get () { return this._routerRoot._route }
})
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
复制代码


在此通过添加原型方法的方式往vue实例中注入router和router和routerroute对象,这就解释了为什么每个组件实例都可以拿到这两个对象或方法。


同时通过Vue.component注册了全局组件RouterView及RouterLink

// install
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)
  }
})
复制代码


在install中还有个比较重要的就是通过Vue.mixin为实例混入执行逻辑,这边有几个关键的点


  1. 将_routerRoot设置为根实例,并且子组件通过$parent来获取
if (isDef(this.$options.router)) {
  this._routerRoot = this
} else {
  this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
复制代码

  1. 从根实例配置获取router,并通过init方法进行初始化。联系上一步的行为可以得知这边就是将组件实例this和router实例绑定了。所以每个组件都可以通过this._routerRoot获取根实例,并且获取其中的router实例。

// $options.router即我们在new Vue中传入的router
this._router = this.$options.router
// 通过init进行初始化 这边还传入了组件实例this
this._router.init(this)
复制代码

  1. 通过vue的defineReactive函数实现数据监听。这里巧妙地使用defineReactive将_routerRoot的_route属性设置为响应式数据,实际在routerview组件中会访问_route属性。所以在我们修改当前路由_route时候就会触发render函数的重新执行,其实也就重新渲染routerview。

Vue.util.defineReactive(this, '_route', this._router.history.current)
复制代码


VueRouter实现


接着我们来看看VueRouter的重要逻辑,在indexJS中。


在构造函数中会初始化一些重要变量。

// VueRouter constructor
// 保存实例对应的根组件
this.app = null
this.apps = []
// router实例化传入的配置
this.options = options
// 用于收集全局守卫函数的数组
this.beforeHooks = []
this.resolveHooks = []
this.afterHooks = []
// createMatcher将options.routes作为参数传入
// 将路由配置routes进一步处理生成pathList pathMap nameMap
// 我们在后面将分析createMatcher
this.matcher = createMatcher(options.routes || [], this)
复制代码


再来看看对于路由模式的处理,比较简单


// VueRouter constructor
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
// 对于不同路由模式将实例化不同的路由类,如history模式对应HTML5History
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}`)
    }
}
复制代码


我们再简单看下几个主要的实例方法


  1. init函数将router实例和根组件实例app进行绑定

init(app) {
  this.apps.push(app)
  if (this.app) {
    return
  }
  this.app = app
  const history = this.history
  // 比较重要的一步
  // 路由的修改处理逻辑实际是通过history实例来处理的
  // 这边通过history暴露出的listen方法对路由变化进行监听
  // 当路由修改时将调用回调函数更新app._route
  // 这就和上面插件安装步骤中将_router定义为响应式数据联系起来了
  // 当history改变->app._route修改->routerview重新渲染->更新当前显示的组件
  history.listen(route => {
    this.apps.forEach(app => {
      app._route = route
    })
  })
}
复制代码

  1. match函数进行路由匹配,实际vuerouter将routes的处理和匹配都交给了matcher进行处理。可以理解为将这一部分内容抽离了。

match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route {
  return this.matcher.match(raw, current, redirectedFrom)
}
复制代码

  1. push和replace等方法。我们知道可以通过router实例调用push方法来切换路由,看看其在router实例中的实现逻辑。

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 {
    // 实际router这里并不会直接处理路由修改的逻辑
    // 而是将其转交给history进行处理
    this.history.push(location, onComplete, onAbort)
  }
}
复制代码


Matcher相关逻辑


前面有提到,Matcher用于处理routes及进行路由匹配的逻辑,实际主要逻辑是生成record及pathList等,我们来看看其相关的几个重要逻辑。


createRouteMap


  1. 通过createRouteMap将配置routes转化为数组及对象数组
// createMatcher
const { pathList, pathMap, nameMap } = createRouteMap(routes)
复制代码
export function createRouteMap (
  routes: Array<RouteConfig>,
  oldPathList?: Array<string>,
  oldPathMap?: Dictionary<RouteRecord>,
  oldNameMap?: Dictionary<RouteRecord>,
  parentRoute?: RouteRecord
): {
  pathList: Array<string>,
  pathMap: Dictionary<RouteRecord>,
  nameMap: Dictionary<RouteRecord>
} {
  // 这边可以留意oldPathList oldPathMap oldPathMap这里其实利用了引用数据来递归生成pathList等
  const pathList: Array<string> = oldPathList || []
  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
  const nameMap: Dictionary<RouteRecord> =   || Object.create(null)
  // 实际会进一步执行addRouteRecord来往pathList等添加数据
  routes.forEach(route => {
    addRouteRecord(pathList, pathMap, nameMap, route, parentRoute)
  })
  // *号会最后匹配的原理就是这里单独拎出来处理了
  for (let i = 0, l = pathList.length; i < l; i++) {
    if (pathList[i] === '*') {
      pathList.push(pathList.splice(i, 1)[0])
      l--
      i--
    }
  }
  return {
    pathList,
    pathMap,
    nameMap
  }
}
复制代码


addRouteRecord


再来看看addRouteRecord的逻辑


首先定义RouteRecord

// RouteRecord实际对应我们在开发中看到的route对象
// addRouteRecord的核心逻辑之一就在于将routes转化为addRouteRecord
const record: RouteRecord = {
  path: normalizedPath,
  regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
  components: route.components || { default: route.component }, // 对应的组件
  alias: route.alias
    ? typeof route.alias === 'string'
      ? [route.alias]
      : route.alias
    : [],
  instances: {}, // 组件实例
  enteredCbs: {},
  name,
  parent,
  matchAs,
  redirect: route.redirect,
  beforeEnter: route.beforeEnter,
  meta: route.meta || {},
  props:
    route.props == null
      ? {}
      : route.components
        ? route.props
        : { default: route.props }
}
复制代码


再就是将routeRecord加到数组中进行保存了

if (!pathMap[record.path]) {
  pathList.push(record.path)
  pathMap[record.path] = record
}
if (!nameMap[name]) {
  nameMap[name] = record
} 
复制代码


还有个逻辑就是递归了,前面只能说对routes最外层进行处理生成record,但是嵌套路由没有处理,实际是通过深度优先遍历来实现嵌套路由处理的。


route.children.forEach(child => {
  const childMatchAs = matchAs
    ? cleanPath(`${matchAs}/${child.path}`)
    : undefined
  addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
})
复制代码


addRoutes


我们知道vueRouter可以通过addRoutes来实现动态路由,实际其原理很简单。


function addRoutes (routes) {
  createRouteMap(routes, pathList, pathMap, nameMap)
}
复制代码


因为pathList和pathMap,nameMap是引用类型,实际再调用createRouteMap生成对应record并加入到其中就可以了。这也解释了一个东西,我们可以添加新的路由,但是不能去更新已有的路由。


match


match就很简单了,通过前面的pathList,pathMap,nameMap查找到对应的record就行,这边不另外分析。


结语


本篇文章简单记录了下vueRouter中几个主要文件的关键逻辑。为避免篇幅过长看着太过枯燥将history及component的分析放在下一篇进行分析。




相关文章
|
3月前
|
JavaScript
vue-router(路由嵌套)
该博客文章展示了如何在Vue.js项目中使用Vue Router实现路由嵌套,并结合Element UI组件库来创建具有导航和下拉菜单的界面,以及如何使用`<router-link>`与`<el-dropdown-item>`实现导航链接。
vue-router(路由嵌套)
|
前端开发
react实现步进器
react实现步进器
|
JavaScript
为什么 Vue3 的 VNode 不能单独组成一棵完整的树?
为什么 Vue3 的 VNode 不能单独组成一棵完整的树?
86 0
|
算法
vueRouter简记(下)
「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」
|
Web App开发 算法 JavaScript
vue-router 还给路由排了序?解析路由匹配,vue-router Matcher 解析(三)
之前的两篇文章讲了, vue-router 的 Matcher 对初始的 routes 进行了标准化(normalized)处理以及别名(alias)处理,但还有matcher 处理 route 的匹配
|
JavaScript
【多级路由使用】Vue中路由嵌套
在之前的一级路由中,我们解决了原生中的抖动问题,以及代码冗余问题,但是现在还有一个问题没有解决,那就是如果有多个需要跳转的那该怎么办呢?
128 0
|
设计模式 JavaScript
学习Vue3 第二十四章(兄弟组件传参和Bus)
A 组件派发事件通过App.vue 接受A组件派发的事件然后在Props 传给B组件 也是可以实现的缺点就是比较麻烦 ,无法直接通信,只能充当桥梁
209 0
|
JavaScript
vue mixins混入优先级 局部全局混入
对于data定义属性,组件中定义属性覆盖mixins中同名字段 对于methods中的同名方法,组件内的方法覆盖mixins中的方法 对于相同的computed属性,组件的computed属性覆盖mixins内的computed属性 对于created、mounted等生命周期函数,mixins中生命周期函数优先执行(执行顺序按mixins中顺序),再执行组件中生命周期函数 watch监听,mixins中的watch监听先执行。
482 0
vue mixins混入优先级 局部全局混入
|
JavaScript
vue再读84-vue-router嵌套路由
vue再读84-vue-router嵌套路由
70 0
vue再读84-vue-router嵌套路由
|
JavaScript
vue再读43-局部自定义指令
vue再读43-局部自定义指令
74 0
vue再读43-局部自定义指令