Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vue-router v2.8源码分析 #6

Open
shen-zhao opened this issue Apr 25, 2021 · 0 comments
Open

vue-router v2.8源码分析 #6

shen-zhao opened this issue Apr 25, 2021 · 0 comments

Comments

@shen-zhao
Copy link
Owner

shen-zhao commented Apr 25, 2021

写得比较匆忙,history、路由调度没有完成,未完待续...

路由安装(install)

路由安装利用了Vue.minxin全局注册了生命周期钩子,以及处理一些运行时的逻辑

// router注册
const router = createRouter({/* options */})

const vm = Vue({
	// ...
  router
})
// install.js
Vue.mixin({
  beforeCreate () {
    // 组件选项只有包含router,才会执行init,并且注册响应式的currentRoute信息
    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 {// 否则向子组件传递routerRoot引用
      this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
    }
    // 这个方法通过routerView用来连接matcher和组件实例,后续会通过组件实例调用生命周期钩子函数
    registerInstance(this, this)
  },
  destroyed () {
    registerInstance(this)
  }
})

Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

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

小技巧:有时我们会有自定义弹窗的逻辑,使用Vue.extend生成并手动插入dom,如果弹窗中如果需要使用router或store的时候,也需要像根组件一样对router、store进行注册。

路由安装阶段,注册了很多运行时的逻辑,其中包括了

  • 注册routerRoot
  • $route的get、set
  • 组件实例化时与router-view的绑定与连接(registerInstance)

保存组件实例是为了更新时调用组件的声明周期钩子

const registerInstance = (vm, callVal) => {
    let i = vm.$options._parentVnode
    // 这个操作是在确认一件事情
    // 就是当前组件的父组件是否为router-view组件,如果是的话,调用这个router-view中的registerRouteInstance方法
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }

路由初始化

  • 初始化参数选项
  • 初始化matcher(createMatcher)
  • 初始化History(history/hash/abstract)

1. 初始化matcher

a. 初始化matcher的第一步,先对用户的路由表(routers)进行处理

递归处理返回三个数据:

  • pathList 用来控制路由匹配的优先级
  • pathMap 以path为键的路由map
  • nameMap 以name为键的路由map
    分别对应下面三种结构:
type PathList = Array<string>

type pathMap = {
	[path: string]: RouteRecord
}

type nameMap = {
	[name: string]: RouteRecord
}

interface RouteRecord = {
  path: string; // 路由路径
  regex: RouteRegExp; // 路由匹配正则
  components: object; // 路由组件map
  instances: object; // 路由组件实例map
  name?: string; // 路由名称
  parent?: RouteRecord; // 父级路由
  redirect?: RedirectOption;
  matchAs?: string;
  beforeEnter: NavigationGuard; // 路由守卫
  meta: any;
  props: boolean | Object | Function | Array<boolean | Object | Function>;
}

b. 创建match函数(用于后续的实时匹配)

match 函数是一个闭包,处于 createMatcher 作用域内,所以可以使用第一步处理routes产生的三个数据结构
match主要逻辑分为两个分支:

  • 首先根据name匹配: 匹配时会组装动态params
  • 其次根据path匹配:根据pathList匹配,并解析动态params
    match函数返回值为route,由createRoute函数返回,究竟返回什么结构呢?
interface Route = {
  path: string;
  name: ?string;
  hash: string;
  query: object;
  params: object;
  fullPath: string;
  matched: Array<RouteRecord>;
  redirectedFrom?: string;
  meta?: any;
}

这个结构就是真实的路由结果,除了包括路由的初始参数和元信息外,还有用户参数数据、全路径、匹配结果等信息
在创建route的过程中,最重要的一个环节就是获取record匹配结果:

路由创建方法,逻辑比较简单

function createRoute (
  record: ?RouteRecord,
  location: Location,
  redirectedFrom?: ?Location,
  router?: VueRouter
): Route {
  const stringifyQuery = router && router.options.stringifyQuery

  let query: any = location.query || {}
  try {
    query = clone(query)
  } catch (e) {}

  const route: Route = {
    name: location.name || (record && record.name),
    meta: (record && record.meta) || {},
    path: location.path || '/',
    hash: location.hash || '',
    query,
    params: location.params || {},
    fullPath: getFullPath(location, stringifyQuery),
    matched: record ? formatMatch(record) : [] // 就是这里
  }
  if (redirectedFrom) {
    route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
  }
  return Object.freeze(route)
}

formatMatch来获取匹配结果,我们来见识一下

function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
  const res = []
  while (record) {
    res.unshift(record)
    record = record.parent
  }
  return res
}

原来这里逻辑这么简单,关键点在于创建路由record的时候,子路由保存了父级路由的引用,通过循环我们一步一步找到这个路由的所有上层路由的record,对于返回结果的顺序,是父辈在左子辈在右,这个顺序对于匹配组件在router-view的渲染至关重要,因为我们知道,vue-router的渲染规则是根据router-view的嵌套深度决定的,这个嵌套深度与匹配结果是一致的。

这里我们直接跳到router-view组件里面一探究竟,来印证一下:

router-view component render

// determine current view depth, also check to see if the tree
// has been toggled inactive but kept-alive.
let depth = 0
let inactive = false
while (parent && parent._routerRoot !== parent) {
  if (parent.$vnode && parent.$vnode.data.routerView) {
    depth++
  }
  if (parent._inactive) {
    inactive = true
  }
  parent = parent.$parent
}
data.routerViewDepth = depth

// render previous view if the tree is inactive and kept-alive
if (inactive) {
  return h(cache[name], data, children)
}

const matched = route.matched[depth]

上面的代码就是router-view组件的实现逻辑,我们发现组件渲染时,通过对父辈组件的遍历来识别是否有父辈router-view组件,一直遍历到routerRoot,得到的深度depth,就是当前router-view组件的深度,最后通过depth为下标取出匹配结果种的目标结果,到这里,真实需要渲染的组件也就得到了。

初始化History

history有三种模式:

  • hash
  • history
  • abstract
    前两种常见与浏览器,提供的api:
  • push/replace
  • go
  • transitionTo(internal)
  • ...
    导航方式:
  • push/replace:命令式,执行匹配 => 渲染组件 => 修改url
  • 被动式:浏览器导航:前进后退、location.assign/replace,触发hashchange/popstate事件 => 执行匹配 => 渲染组件

注意:调用pushstate不会触发popstate事件

运行时

history驱动路由的匹配和渲染:

  • 页面初始导航直接调用transitionTo
  • redirect: 根据路由表配置,在match过成功修改route
  • 浏览器行为:后退、前进
  • 浏览器api:location.assign/replace
    匹配成功后更新当前激活的路由记录record,由于在router在安装时已经订阅了route的响应式属性,所以有使用route的组件(router-view或其他)在渲染过成功也被成功的订阅(依赖收集),所以当路由修改时,也会触发组件的重新渲染
@shen-zhao shen-zhao changed the title Vue-Router V 2.8源码 vue-router v2.8源码分析 Apr 25, 2021
@shen-zhao shen-zhao removed the 源码 label May 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant