VueRouter
+options
+data
+routeMap
+Constructor(Options)
_install(Vue)
+init()
+initEvent()
+createRouteMap()
+initComponents()
  • 类对象: VueRouter

  • 类属性:
    +options: 记录构造函数中传入的对象,routes记录传入规则
    +data:对象,里面有个current,current记录路由地址,响应式对象,路由地址发生变化对应的组件要自动更新,如何把data设置成响应式的呢?vue.obsever这个方法
    +routeMap:传入对象,记录路由中地址和组件的对应关系,将来我们会把路由规则解析到 routeMap

  • 类方法:
    +对外公开的方法,_静态方法
    +Constructor(Options): 构造函数,帮我们初始化刚刚说过的属性,然后是几个初始化的方法
    _install(Vue): 静态方法,实现vue的插件机制
    +init(): 用来调用下面三个方法,这里把不同的代码分隔到不同的方法中来实现
    +initEvent(): 用来注册 popsstate 这个事件,监听浏览器历史的变化
    +createRouteMap(): 初始化 routeMap ,它把构造函数中传入的路由规则转换成键值对的形式存储到 routeMap 来, routeMap 就是一个对象,键就是我们的路由地址,值就是对应的组件
    +initComponents(): 用来创建 routeLink 和 routeView 这两个组件的

Vue 的构建版本

  • 运行时版:不支持 template 模板,需要打包的时候提前编译
  • 完整版:包含运行时和编译器,体积比运行时版大10K 左右,程序运行的时候把模板转换成 render 函数

Vue-Router

演示基本效果

Vue-Router 源码结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qboPtArQ-1667316041020)(assets/image-20200724094119058.png)]

Vue.use() 注册插件源码

  • src\core\global-api\use.js
  • return this 的作用是方便后续的链式调用
export function initUse (Vue: GlobalAPI) {
  // Vue.use(VueRouter, options)
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this // return this 的作用是方便后续的链式调用
    }

    // additional parameters
    // 把数组中的第一个元素(plugin)去除
    const args = toArray(arguments, 1)
    // 把this(Vue)插入第一个元素的位置
    args.unshift(this)
    //  args --> [Vue, options]
    if (typeof plugin.install === 'function') {
      // plugin.install(...args)
      plugin.install.apply(plugin, args)  // plugin.install(args[0], args[1])
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

模拟整体结构

VueRouter 基本结构

export default class VueRouter {
  constructor (options) {
    // 记录所有的路由规则
    this._routes = options.routes || []
  }
  
  init () {}
}

install 方法

  • 注册 VueRouter 插件,并给 Vue 根实例,以及每一个子组件对象设置 _routerRoot ,让子组件可以获取到根实例,以及根实例中存储的 _router 对象
export let _Vue = null
export default function install (Vue) {
  _Vue = Vue
  _Vue.mixin({
    beforeCreate () {
      // 判断当前是否是 Vue 的根实例
      if (this.$options.router) {
        this._router = this.$options.router
        // 根实例记录自己,目的是在子组件中通过 _routerRoot 获取到 _router 对象
        this._routerRoot = this
        this._router.init(this)
      } else {
        // 给子组件设置 routerRoot,让子组件能够通过 routerRoot 找到 _router 对象
        this._routerRoot = this.$parent && this.$parent._routerRoot
      }
    }
  })
}
  • 挂载 install
import install from './intall'
export default class VueRouter {
  constructor (options) {
    // 记录所有的路由规则
    this._routes = options.routes || []
  }

  // 初始化事件监听器,监听路由地址的变化
  // 改变 url 中的路由地址
  init (app) {
  }
}
VueRouter.install = install

router-link、router-view

  • 此时创建这两个组件的目的是为了测试
  • router-link
export default {
  props: {
    to: {
      type: String,
      required: true
    }
  },
  // template: `<a :href="'#' + this.to"><slot name="default"></slot></a>`
  render (h) {
    return h('a', { attrs: { href: '#' + this.to } }, [this.$slots.default])
  }
}
  • router-view
export default {
  render (h) {
    return h()
  }
}

createMatcher 和 createRouteMap

createMatcher
  • 创建并返回一个匹配器,包含 match 方法和 addRoutes 方法
    • match 根据路由地址匹配相应的路由规则对象
    • addRoutes 动态添加路由
  • 把所有的路由规则解析成路由表
    • pathList 是一个数组,存储所有的路由地址
    • pathMap 路由表,路由地址 -> record 一个记录(path、component、parent)
export default function createMatcher (routes) {
  // routes 所有的路由规则
  // 把路由规则解析成数组和对象的形式存储到 pathList pathMap
  const { pathList, pathMap } = createRouteMap(routes)

  function match (path) {

  }
  function addRoutes (routes) {
		createRouteMap(routes, pathList, pathMap)
  }
  return {
    match,
    addRoutes
  }
}
createRouteMap
  • 遍历所有的路由规则,生成路由表
  • 如果有子路由的话,递归添加子路由到路由表
export default function createRouteMap (routes, oldPathList, oldPathMap) {
  const pathList = oldPathList || []
  const pathMap = oldPathMap || {}
  // 遍历路由规则,解析成路由表
  routes.forEach(route => {
    addRouteRecord(route, pathList, pathMap)
  })
  return {
    pathList,
    pathMap
  }
}
// 添加路由表
function addRouteRecord (route, pathList, pathMap, parent) {
  const path = parent ? `${parent.path}/${route.path}` : route.path
  const record = {
    path: path,
    component: route.component,
    parent // 如果是子路由的话,记录子路由的 parent record
  }

  // 如果路由表中有已经有该路径,不做处理
  if (!pathMap[path]) {
    pathMap[path] = record
    pathList.push(path)
  }
  // 如果有子路由,递归添加到对应的 pathList 和 pathMap 中
  if (route.children) {
    route.children.forEach(childRoute => {
      addRouteRecord(childRoute, pathList, pathMap, record)
    })
  }
}
createMatcher – match
  • 根据路由地址,匹配一个路由数据对象 route { matched, path }
    • create-matcher.js 中
function match (path) {
  const record = pathMap[path]
  if (record) {
    return createRoute(record, path)
  }
  return createRoute(null, path)
}
  • createRoute 根据路由地址,创建 route 路由规则对象
    • route --> { matched: [ musicRecord ], path: ‘/music’ }
    • 如果是子路由的话,找到他的所有父路由对应的 record 插入到数组的第一项中
    • matched 数组中 -> [musicRecord, popRecord]
function createRoute (record, path) {
  const matched = []
  while (record) {
    matched.unshift(record)
    record = record.parent
  }
  return {
    matched,
    path
  }
}
  • VueRouter 的构造函数中
// createMatcher 返回 match 匹配的方法 和 addRoutes 动态添加路由的方法
this.matcher = createMatcher(routes)

History 历史管理

  • hash 模式

  • html 5 模式

  • History 父类

    • router 属性
    • current 属性,记录当前路径对应的路由规则对象 {path:‘/’, matched: []}
    • transitionTo(path, onComplete)
      • 跳转到指定的路径,根据当前路径获取匹配的路由规则对象 route,然后更新视图
export default class History {
  constructor (router) {
    this.router = router
    // 当前路径获取到的匹配的结果
    //  { path:'/', matched: [] }
    this.current = createRoute(null, '/')
  }
  transitionTo (path, onComplete) {
    // 根据路径获取匹配到的路由规则对象,渲染页面
    // { path: '/music/pop', matched: [musicRecord, popRecord] }
    this.current = this.router.matcher.match(path)
    console.log(path, this.current)
    onComplete && onComplete()
  }
}
  • HashHistory
    • 继承 History
    • 确保首次访问地址为 #/
    • getCurrentLocation() 获取当前的路由地址(# 后面的部分)
    • setUpListener() 监听路由地址改变的事件
import History from './base'
export default class HashHistory extends History {
  constructor (router) {
    super(router)
    // 如果是第一次访问设置为首页 #/
    ensureSlash()
  }
  getCurrentLocation () {
    return window.location.hash.slice(1)
  }
  setUpListener () {
    window.addEventListener('hashchange', () => {
      this.transitionTo(this.getCurrentLocation())
    })
  }
}
function ensureSlash () {
  // 判断#后面有内容
  if (window.location.hash) {
    return
  }
  window.location.hash = '/'
}
  • VueRouter 构造函数中初始化 history
    • 根据创建 VueRouter 传来的 mode 决定使用哪个 History 对象
const mode = this.mode = options.mode || 'hash'
switch (mode) {
  case 'hash':
    this.history = new HashHistory(this)
    break
  case 'history':
    this.history = new HTML5History(this)
    break
  default:
    throw new Error('mode error')
}
  • VueRouter 的 init 中调用
// 初始化事件监听器,监听路由地址的变化
// 改变 url 中的路由地址
init (app) {
  const history = this.history
  const setUpListener = _ => {
    history.setUpListener()
  }
  history.transitionTo(
    history.getCurrentLocation(),
    setUpListener
  )
}

// install 中调用 init()

给 router 对象设置响应式的 _route 属性

  • 参考源码,在 install.js 中
Vue.util.defineReactive(this, '_route', this._router.history.current)
  • 让 _route 改变
  • 在 history/base.js 中
// 增加属性
this.cb = null

// 增加一个 listen 方法
// 在 transitionTo 中调用,触发回调,给 _route 赋值
listen (cb) {
  this.cb = cb
}

// transitionTo 方法中
transitionTo (path, onComplete) {
  this.current = this.router.matcher.match(path)
  
	// 调用 listen 中设置的回调,并且把 最新的 current 传递给 cb
  // cb 中把当前的 current 赋值给 app._route 响应式数据发生变化,更新视图
  this.cb && this.cb(this.current)
  
  onComplete && onComplete()
}

  • VueRouter 中
    • index.js
init () {
  …………
  // init 的最后调用 父类中的 listen 方法
  // 在回调中给 _route 重新赋值,更新视图
  history.listen(route => {
    app._route = route
    console.log(app._route)
  })
}

r o u t e / route/ route/router

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

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

router-view

  • 获取当前组件的 $route 路由规则对象
  • 找到里面的 matched 匹配的 record (里面有 component)
  • 如果是 /music 的话,matched 匹配到一个,直接渲染对应的组件
  • 如果是 /music/pop 的话,matched 匹配到两个 record(第一个是父组件,第二个是子组件)
render (h) {
  // 根据路径找到 route ,看里面的 matched 有几个
  // this.$route
  let depth = 0
  const route = this.$route

  // 标识当前组件是一个 router-view
  this.routerView = true

  let parent = this.$parent
  while (parent) {
    // 如果当前组件的父组件也是 router-view 这时候让depth++
    if (parent.routerView) {
      depth++
    }
    parent = parent.$parent
  }

  const record = route.matched[depth]
  if (!record) {
    return h()
  }
  const component = record.component
  return h(component)
}
Logo

前往低代码交流专区

更多推荐