Vue深入学习5—Vue-Router路由模式

路由源码思维导图
1、Vue-Router三种路由模式:
  • hash:使用URL hash 值来做路由,支持所有路由器
  • history: 依赖HTML5 History API和服务器配置;
  • abstract: 支持所有JS运行环境,Node.js服务端;

1.1、路由作用:根据不同的路径,来映射到不同的视图;

1.2、路由基本使用

<div id="app">
    <h1>Hello  kuishou!</h1>
    <p>
        <!--<router-link>默认会被渲染成一个`<a>`标签-->
        <router-link to="/foo"> 睡觉 Foo</router-link>
        <router-link to="/bar"> 敲代码 bar</router-link>
    </p>
    <!--路由匹配到的组件将渲染在这里-->
    <router-view></router-view>
</div>
import Vue from 'vue'
import VueRouter from  'vue-router'
// 注册路由
Vue.use(VuerRouter)
// 1.定义组件
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 2.定义路由
const routes = [
    { path: '/foo', components: Foo },
    { path: '/bar', components: Bar },
]
2路由注册:

2.1、Vue插件的注册原理: 每个插件都需要实现一个静态的 install 方法,当我们执行 Vue.use 的时候,就会执行这个 install 方法,并且在这个 install 方法中第一个参数拿到 Vue 对象。

3、路由安装:

Vue-Router 安装最重要的一步就是利用 Vue.mixin 去把 beforeCreatedestroyed 两个钩子函数注入到每一个组件中,在beforeCreateed 中定义 私有属性和初始化 路由。

// install.js
// 把  _Vue  export 出去,在源码的任何地方都可以访问 Vue
export let _Vue

export function install (Vue) {
    // 判断是否有注册指令,如果多次执行install方法,则会return
  if (install.installed && _Vue === Vue) return
  install.installed = true
    // 使用下划线 _Vue 保留 传过来的Vue
  _Vue = Vue
  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)
    }
  }
  // mixin 作用:把mergeOptions 扩展到全局的 options 
  Vue.mixin({
      // 这样的话,每一个组件都有beforeCreate、destroyed这两个钩子函数
    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)
    }
  })
3、VueRouter对象:

当我们执行 new VueRouter 时,beforeCreated 钩子函数会执行 router.init 方法,

 constructor (options: RouterOptions = {}) {
    this.app = null  // 根 Vue 实例
    this.apps = []   // 保存所有子组件的 Vue 实例
    this.options = options // 保存传入的路由配置
    this.beforeHooks = []  // 钩子函数
    this.resolveHooks = [] // 钩子函数
    this.afterHooks = []   // 钩子函数
    // 路由匹配器
    this.matcher = createMatcher(options.routes || [], this)
    // 路由创建的三种模式: hash、history、abstract
    let mode = options.mode || 'hash'
    // 路由创建失败的回调函数,检测浏览器中有没有历史记录(history)
    this.fallback =
      mode === 'history' && !supportsPushState && options.fallback !== false
    // 路由历史的具体的实现实例, 如果没有则会使用hsah访问
    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}`)
        }
    }
  }
4、Matcher

路由匹配器,主要通过 matchermatch方法 ,匹配路径 Router 的.

  • 4.1、createRouteMap 函数是把用户的 路由配置 转换成一张 路由映射表
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>
} {
  // 路径列表用于控制路径匹配优先级
  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, parentRoute)
  })
  • 4.1、createMatcher的初始化逻辑

createMatcher 首先执行的逻辑是 ````const { pathList, pathMap, nameMap } = createRouteMap(routes) ``` 用来创建一个映射表

  // 对路由数组进行遍历
  routes.forEach(route => {
    // 遍历成功·拿到每个路由对象
    addRouteRecord(pathList, pathMap, nameMap, route, parentRoute)
  })

  • 4.3、match 的匹配过程

match 方法作用:根据传入的 raw 和当前的路径 currentRoute 计算一个新的路径并返回。

match 方法接收3个参数:raw(Location 对象)、currentRoute(当前的路径)、redirectedFrom(与重定向相关

function match (
    raw: RawLocation, // url 字符串,也可以是⼀个 Location 对象
    currentRoute?: Route, // Router 类型,表示当前的路径
    redirectedFrom?: Location // 与重定向相关
  ): Route {
    // 根据 raw , current 计算出新的 location 
    const location = normalizeLocation(raw, currentRoute, false, router)
    const { name } = location
    // 如果current传入属性有name
    if (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)

      if (typeof location.params !== 'object') {
        location.params = {}
      }

      if (currentRoute && typeof currentRoute.params === 'object') {
        for (const key in currentRoute.params) {
          if (!(key in location.params) && paramNames.indexOf(key) > -1) {
            location.params[key] = currentRoute.params[key]
          }
        }
      }

      location.path = fillParams(record.path, location.params, `named route "${name}"`)
      return _createRoute(record, location, redirectedFrom)
    } else if (location.path) {
      location.params = {}
      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)
        }
      }
    }
    // no match
    return _createRoute(null, location)
  }

5、路径切换

发生路径切换的时候,执行的一系列钩子函数。

导航守卫三兄弟
  • 5.1、导航守卫的执行流程:

    Vue项目中,导航被触发后,失活的组件(叛变的人)开始调用beforeRouteLeave ,全局守卫(大哥) beforeEach 、组件内的守卫(三弟)重用组件 beforeRouterUpdate 被逐步触发;路由守卫(二哥)在路由配置里调用 beforeEnter 后开始解析异步路由组件;在被激活的目标组件(敌人)里调用beforeRouteEnter ;全局守卫(大哥)beforeResolve检测到目标组件(敌人)被激活(打败),在router.js中查找到需要跳转的导航并被确认,afterEach钩子被调用,最终触发DOM更新;路由守卫(二哥)调用 beforeRouteEnter 传给next的回调函数。

// 全局守卫
router.beforeEach((to, from, next)=>{
    // 进入路由前首先检查是否登录,如果没有则跳转到登录的视图组件
    if(to.name != 'Login' && !isAuthenticated) next({ 
        name: 'Login'
    // 否则继续下一个脚本
    }) else  {
        next()
    }
}) 
完整的导航解析流程图:参考官网绘制

参考:https://www.jianshu.com/p/60da87d4ec92

官方文档:Vue-Router

守卫识别路由的三把钥匙:

to : 即将进入的路由

from : 即将离开的路由

next : 进行管道中的下一个钩子

面试题:给路由组件传递数据有哪几种方式?

  • 通过 params 传递
// params 不能与 path 一起使用
router.push({ path: './details', parmas: { id: '001'} }) // ->跳转到details
  • 2、通过 query 传递
this.$router.push({ path: '/details/001', query: { kind: "car" }})
  • 3、通过 hash传递
this.$touter.push({ path: './details001', hash: '#car'})

...

参考: Vue 路由组件传参的 8 种方式

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,302评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,563评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,433评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,628评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,467评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,354评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,777评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,419评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,725评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,768评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,543评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,387评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,794评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,032评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,305评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,741评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,946评论 2 336

推荐阅读更多精彩内容