vue-router源码解析

vue-router简介

vue-router工作原理:

  1. url改变
  2. 触发监听事件 (原理见路由模式)
  3. 改变vue-router里面的current变量
  4. vue监听current的监听者
  5. 获取到新的组件
  6. render新组件

vue-router如何实现无刷新页面切换:

  1. 采用某种方式使url发生改变。这种方式可能是调用HTML5 history API实现,也可能是点击前进后退或者改变路由hash.但是不管采用哪种方式,它都不能造成浏览器刷新,仅仅只是单纯的url发生变化.
  • history.pushState(state,title,url) :
    无刷新的向当前history插入一条历史状态,但是并不会造成页面的重新加载和浏览器向服务器发送请求
  • window.onpopState() :
    当点击后退,前进按钮,或调用history.go()方法时,该方法会被触发,但是history.pushState并不会直接触发popstate.
    回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前 URL 所提供的状态对象(即这两个方法的第一个参数)这个state对象也可以直接通过history对象读取
window.onpopstate = function (event) {//
  console.log('location: ' + document.location);
  console.log('state: ' + JSON.stringify(event.state));
};
  1. 监听到url的变化之后,根据不同的路径获取渲染内容,再把内容填充到div容器里.从上面案例可知,监听url的变化一般在两个地方,第一是在window.onpopstate包裹的回调函数里,第二是在执行history.pushState或history.replaceState的后面. render函数根据跳转路径的不同动态改变app容器里面的内容,从而便模拟出了点击不同路径页面似乎发生了跳转的效果.
  • render:function(createElement){return createElement(APP)} 其中的形参是一个方法,作用是根据给定的组件渲染,把给定的组件渲染到el区域中

路由模式

  • hash模式
    哈希路径中带个#,#后面的就是hash的内容;
    可以通过location.hash拿到
    可以通过onhashchange监听hash的改变

  • history模式
    正常路径,没有#
    可以通过location.pathname拿到
    可以通过onpopstate监听history的改变

深入源码

例子

先来看看在vue中怎么用vue-router
import vue和vue-router后,
用vue.use(vueRouter)来注册组件
而这个vue.use函数又会执行vue-router.install函数

import Vue from 'vue'
import VueRouter from '../c-router' // 引用
import home from '../views/Home.vue'

Vue.use(VueRouter)//1 注册插件
//作用
//1. 执行里面方法
//2. 如果这个方法有一个属性install 并且这个属性是一个方法, Vue.use就会执行install方法
// 如果没有install方法, 就会执行这个父级方法(vuerouter)本身
//3. install这个方法的第一参数是vue(就是vue的构造函数)

const routes = [
	{
		path: '',
		name: 'Layout',
		children: [
			{
				path: '/home',
				name: 'Home',
				component: Home
			},
			{
				path: '/about',
				name: 'About',
				component: () => import('../viewa/About.vue')
			}
		]
	}
]

const router = new VueRouter({
	mode:'hash',
	routes
})

vue中怎么注册vue-router

再来看看源码

先来看看vue2源码中的initUse函数,其中声明了Vue.use函数
路径:src/core/global-api/use.ts

import type { GlobalAPI } from 'types/global-api'
import { toArray, isFunction } from '../util/index'

export function initUse(Vue: GlobalAPI) {
//注意看这里的Vue.use, 就是 之前vue中怎么调用vue-router 小节中,我们用到的Vue.use(VueRouter)
  Vue.use = function (plugin: Function | any) {//plugin:插件
	//重复注册插件的情况:
    const installedPlugins = this._installedPlugins || (this._installedPlugins = [])
    if (installedPlugins.indexOf(plugin) > -1) {
      return this//若已经注册过(用indexof能找到),直接返回
    }

    // additional parameters
    const args = toArray(arguments, 1)//args就是之前vue中怎么调用vue-router 小节中的install的参数
    args.unshift(this) //this就是vue的实例
    if (isFunction(plugin.install)) {//如果plugin的install属性是方法的话
      plugin.install.apply(plugin, args)//调用
    } else if (isFunction(plugin)) {//若无
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

vue-router中install方法实现 https://github.com/vuejs/vue-router/tree/dev
路径:dist/vue-router.js

  function install (Vue) {
  //判断是否已经安装过插件
    if (install.installed && _Vue === Vue) { return }
    install.installed = true;

    _Vue = Vue;
//辅助函数,判断一个值是否已定义
    var isDef = function (v) { return v !== undefined; };
//注册路由实例的辅助函数
    var registerInstance = function (vm, callVal) {
      var i = vm.$options._parentVnode;
      if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
        i(vm, callVal);
      }
    };
    
//全局混入,在组件的beforeCreate和destroyed生命狗子里执行一些操作
    Vue.mixin({
      beforeCreate: function beforeCreate () {
        if (isDef(this.$options.router)) {//如果组件定义了$options.router,则表示当前组件是根组件
          this._routerRoot = this;
          this._router = this.$options.router;
          this._router.init(this);//初始化路由
          Vue.util.defineReactive(this, '_route', this._router.history.current);//定义响应式的_route属性,有了这个响应式的路由对象,就可以在路由更新的时候及时的通知RouterView去更新组件了
        } else {//如果不是根组件,则将_routerRoot指向最近的父级根组件
          this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
        }
        registerInstance(this, this);// 注册路由实例
      },
      destroyed: function destroyed () {
        registerInstance(this);// 销毁时注销路由实例
      }
    });
    
// 在Vue原型上定义$router属性的访问器
    Object.defineProperty(Vue.prototype, '$router', {
      get: function get () { return this._routerRoot._router }
    });

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

    Vue.component('RouterView', View);// 注册RouterView组件
    Vue.component('RouterLink', Link);// 注册RouterLink组件

    var strats = Vue.config.optionMergeStrategies;
    // use the same hook merging strategy for route hooks
    strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
  }

至此,Vue.use(VueRouter)//1 注册插件 这一步中的相关源码都看完了,我们继续往下

VueRouter类的实例化

来看看vueRouter是怎么实例化的,
路径:src/router.js

import { install } from './install'
import { START } from './util/route'
import { assert, warn } from './util/warn'
import { inBrowser } from './util/dom'
import { cleanPath } from './util/path'
import { createMatcher } from './create-matcher'
import { normalizeLocation } from './util/location'
import { supportsPushState } from './util/push-state'
import { handleScroll } from './util/scroll'
import { isNavigationFailure, NavigationFailureType } from './util/errors'
import { HashHistory } from './history/hash'
import { HTML5History } from './history/html5'
import { AbstractHistory } from './history/abstract'
import type { Matcher } from './create-matcher'

export default class VueRouter {
  static install: () => void
  static version: string
  static isNavigationFailure: Function
  static NavigationFailureType: any
  static START_LOCATION: Route

  app: any
  apps: Array<any>
  ready: boolean
  readyCbs: Array<Function>
  options: RouterOptions
  mode: string
  history: HashHistory | HTML5History | AbstractHistory
  matcher: Matcher
  fallback: boolean
  beforeHooks: Array<?NavigationGuard>
  resolveHooks: Array<?NavigationGuard>
  afterHooks: Array<?AfterNavigationHook>

  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 = []
    // 创建路由匹配实例;传人我们定义的routes:包含path和component的对象;以防你们忘了是啥,下面是一个新建router的例子
    // const router = new VueRouter({
    //	  mode: 'history',
	//    routes: [{  path: '/',
	//                component: Main, }],
	//});
    this.matcher = createMatcher(options.routes || [], this)

    let mode = options.mode || 'hash'//判断模式是哈希还是history
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false // 判断浏览器是否支持history,如果不支持则回退到hash模式;
    if (this.fallback) {
      mode = 'hash'
    }
    if (!inBrowser) {
      mode = 'abstract'
    }
    this.mode = mode

    switch (mode) {// 根据不同模式创建对应的history实例
      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}`)
        }
    }
  }

来看看createMatcher方法
路径:src/create-matcher.js

import type VueRouter from './index'
import { resolvePath } from './util/path'
import { assert, warn } from './util/warn'
import { createRoute } from './util/route'
import { fillParams } from './util/params'
import { createRouteMap } from './create-route-map'
import { normalizeLocation } from './util/location'
import { decode } from './util/query'

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>;
};

// routes为我们初始化VueRouter的路由配置;router就是我们的VueRouter实例;
export function createMatcher (
  routes: Array<RouteConfig>,
  router: VueRouter
): Matcher {
  const { pathList, pathMap, nameMap } = createRouteMap(routes)//根据新的routes生成路由;pathList是根据routes生成的path数组;pathMap是根据path的名称生成的map;如果我们在路由配置上定义了name,那么就会有这么一个name的Map;

//添加路由
  function addRoutes (routes) {
    createRouteMap(routes, pathList, pathMap, nameMap)
  }

  function addRoute (parentOrRoute, route) {
    const parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined
    // $flow-disable-line
    createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent)

    // add aliases of parent
    if (parent && parent.alias.length) {
      createRouteMap(
        // $flow-disable-line route is defined if parent is
        parent.alias.map(alias => ({ path: alias, children: [route] })),
        pathList,
        pathMap,
        nameMap,
        parent
      )
    }
  }

  function getRoutes () {
    return pathList.map(path => pathMap[path])
  }

//路由匹配函数,根据给定的路由地址信息进行匹配,并返回匹配到的路由对象
  function match (
    raw: RawLocation,// 原始的路由地址信息
    currentRoute?: Route, // 当前路由对象
    redirectedFrom?: Location// 重定向来源的路由地址信息
  ): Route {
    const location = normalizeLocation(raw, currentRoute, false, router)
    const { name } = location

    if (name) { //如果有路由名称
      const record = nameMap[name]// 根据路由名称查找对应的记录
      if (process.env.NODE_ENV !== 'production') {
        warn(record, `Route with name '${name}' does not exist`)
      }
      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)
  }

  function redirect (
    record: RouteRecord,
    location: Location
  ): Route {
    const originalRedirect = record.redirect
    let redirect = typeof originalRedirect === 'function'
      ? originalRedirect(createRoute(record, location, null, router))
      : originalRedirect

    if (typeof redirect === 'string') {
      redirect = { path: redirect }
    }

    if (!redirect || typeof redirect !== 'object') {
      if (process.env.NODE_ENV !== 'production') {
        warn(
          false, `invalid redirect option: ${JSON.stringify(redirect)}`
        )
      }
      return _createRoute(null, location)
    }

    const re: Object = redirect
    const { name, path } = re
    let { query, hash, params } = location
    query = re.hasOwnProperty('query') ? re.query : query
    hash = re.hasOwnProperty('hash') ? re.hash : hash
    params = re.hasOwnProperty('params') ? re.params : params

    if (name) {
      // resolved named direct
      const targetRecord = nameMap[name]
      if (process.env.NODE_ENV !== 'production') {
        assert(targetRecord, `redirect failed: named route "${name}" not found.`)
      }
      return match({
        _normalized: true,
        name,
        query,
        hash,
        params
      }, undefined, location)
    } else if (path) {
      // 1. resolve relative redirect
      const rawPath = resolveRecordPath(path, record)
      // 2. resolve params
      const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
      // 3. rematch with existing query and hash
      return match({
        _normalized: true,
        path: resolvedPath,
        query,
        hash
      }, undefined, location)
    } else {
      if (process.env.NODE_ENV !== 'production') {
        warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)
      }
      return _createRoute(null, location)
    }
  }

  function alias (
    record: RouteRecord,
    location: Location,
    matchAs: string
  ): Route {
    const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`)
    const aliasedMatch = match({
      _normalized: true,
      path: aliasedPath
    })
    if (aliasedMatch) {
      const matched = aliasedMatch.matched
      const aliasedRecord = matched[matched.length - 1]
      location.params = aliasedMatch.params
      return _createRoute(aliasedRecord, location)
    }
    return _createRoute(null, location)
  }

//根据给定的路由记录(record)、路由地址信息(location)和重定向来源(redirectedFrom)来创建一个路由对象(Route)
  function _createRoute (
    record: ?RouteRecord,
    location: Location,
    redirectedFrom?: Location
  ): Route {
    if (record && record.redirect) {
      return redirect(record, redirectedFrom || location)
    }
    if (record && record.matchAs) {
      return alias(record, location, record.matchAs)
    }
    return createRoute(record, location, redirectedFrom, router)
  }

  return {
    match,
    addRoute,
    getRoutes,
    addRoutes
  }
}

function matchRoute (
  regex: RouteRegExp,
  path: string,
  params: Object
): boolean {
  const m = path.match(regex)

  if (!m) {
    return false
  } else if (!params) {
    return true
  }

  for (let i = 1, len = m.length; i < len; ++i) {
    const key = regex.keys[i - 1]
    if (key) {
      // Fix #1994: using * with props: true generates a param named 0
      params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i]
    }
  }

  return true
}

function resolveRecordPath (path: string, record: RouteRecord): string {
  return resolvePath(path, record.parent ? record.parent.path : '/', true)
}

后面的读不动了 有空继续

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/574625.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot+RabbitMQ实现MQTT协议通讯

一、简介 MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它工作在 TCP/IP协议族上&#xff0c;是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议&#xff0c;为此&#xff0c;它需要一个消息中间件 。此…

数据结构之堆

片头 嗨! 小伙伴们,上一篇中,我们学习了队列相关知识,今天我们来学习堆这种数据结构,准备好了吗? 我们开始咯 ! 一、堆 1.1 堆的概念 堆&#xff08;Heap&#xff09;是一种特殊的树,如果将一个集合中的所有元素按照完全二叉树的顺序存储方式存储在一个一维数组中,并满足一定…

物联网:从电信物联开发平台AIoT获取物联设备上报数据示例

设备接入到电信AIoT物联平台后&#xff0c;可以在平台上查询到设备上报的数据。 下面就以接入的NBIOT物联远传水表为例。 在产品中选择指定设备&#xff0c;在数据查看中可以看到此设备上报的数据。 示例中这组数据是base64位加密的&#xff0c;获取后还需要转换解密。 而我…

Oceanbase体验之(一)运维管理工具OCP部署(社区版4.2.2)

资源规划建议 ocp主机1台 内存:64G CPU1:2C及以上 硬盘大于500G observer服务器3台 内存32G CPU&#xff1a;4C以上 硬盘大于1T 建议存储硬盘与操作系统硬盘隔开实现IO隔离 一、OBD、OCP安装包准备 [rootobserver /]# chown -R admin:admin /software/ [rootobserver /]# …

【ensp实验】Telnet 协议

目录 Telnet 协议 telnet协议特点 Telnet实验 ​编辑 不使用console口 三种认证模式的区别 Telnet 协议 Telnet 协议是 TCP/IP 协议族中的一员&#xff0c;是 Internet 远程登录服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用…

【Leetcode每日一题】 穷举vs暴搜vs深搜vs回溯vs剪枝_全排列 - 子集(难度⭐⭐)(65)

1. 题目解析 题目链接&#xff1a;78. 子集 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 算法思路详解&#xff1a; 为了生成数组 nums 的所有子集&#xff0c;我们需要对数组中的每个元素进行“选择”或“不选择…

三星电脑文件夹误删了怎么办?恢复方案在此

在使用三星电脑的过程中&#xff0c;我们可能会不小心删除了某个重要的文件夹&#xff0c;其中可能包含了工作文件、家庭照片、视频或其他珍贵的数据。面对这种突发情况&#xff0c;不必过于焦虑。本文将为您提供几种有效的恢复方案&#xff0c;希望能帮助您找回误删的文件夹及…

微软ML Copilot框架释放机器学习能力

摘要&#xff1a;大模型席卷而来&#xff0c;通过大量算法模型训练推理&#xff0c;能根据人类输入指令产生图文&#xff0c;其背后是大量深度神经网络模型在做运算&#xff0c;这一过程称之为机器学习&#xff0c;本文从微软语言大模型出发&#xff0c;详解利用大型语言模型&a…

【鸿蒙应用】理财App

目录 第一节项目讲解项目介绍 第二节&#xff1a;项目创建登录静态框架编写登录页面设稿新建项目控制台添加项目Login页面封装标题组件 第三节&#xff1a;登录页静态表单编写第四节—内容页架构分析底部栏组件第五节—底部栏组件切换第六节&#xff1a;首页静态页编写第七节&a…

【中级软件设计师】上午题12-软件工程(2):单元测试、黑盒测试、白盒测试、软件运行与维护

【中级软件设计师】上午题12-软件工程&#xff08;2&#xff09; 1 系统测试1.1 单元测试1.2 集成测试1.2.1 自顶向下1.2.2 自顶向上1.2.3 回归测试 2 测试方法2.1 黑盒测试2.1.1 McCabe度量法 2.2 白盒测试2.2.1 语句覆盖-“每个流程”执行一次2.2.2 判定覆盖2.2.3 条件覆盖-A…

BGP的基本概念和工作原理

AS的由来 l Autonomous System 自治系统&#xff0c;为了便于管理规模不断扩大的网络&#xff0c;将网络划分为不同的AS l 不同AS通过AS号区分&#xff0c;AS号取值范围1&#xff0d;65535&#xff0c;其中64512&#xff0d;65535是私有AS号 l IANA机构负责AS号的分发 AS之…

Ubuntu关闭防火墙、关闭selinux、关闭swap

关闭防火墙 打开终端&#xff0c;然后输入如下命令&#xff0c;查看防火墙状态&#xff1a; sudo ufw status 开启防火墙命令如下&#xff1a; sudo ufw enable 关闭防火墙命令如下&#xff1a; sudo ufw disable 关闭selinux setenforce 0 && sed -i s/SELINUXe…

Android kotlin 协程异步async与await介绍与使用

一、介绍 在kotlin语言中&#xff0c;协程是一个处理耗时的操作&#xff0c;但是很多人都知道同步和异步&#xff0c;但是不知道该如何正确的使用&#xff0c;如果处理不好&#xff0c;看似异步&#xff0c;其实在runBloacking模块中使用的结果是同步的。 针对如何同步和如何异…

鸿蒙应用ArkTS开发- 选择图片、文件和拍照功能实现

前言 在使用App的时候&#xff0c;我们经常会在一些社交软件中聊天时发一些图片或者文件之类的多媒体文件&#xff0c;那在鸿蒙原生应用中&#xff0c;我们怎么开发这样的功能呢&#xff1f; 本文会给大家对这个功能点进行讲解&#xff0c;我们采用的是拉起系统组件来进行图片…

03-JAVA设计模式-备忘录模式

备忘录模式 什么是备忘录模式 Java中的备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为型设计模式&#xff0c;它允许在不破坏封装性的前提下捕获一个对象的内部状态&#xff0c;并在该对象之外保存这个状态&#xff0c;以便以后可以将对象恢复到原先保存的状态…

Ansible自动化

Ansible自动化 自动化的需求&#xff1a; 1. 在什么样的场景下需要自动化&#xff1f; 批量化的工作&#xff1a; 装软件包、配置服务、升级、下发文件… 2. 为什么在自动化工具中选择ansible&#xff1f; 对比shell脚本&#xff1a; 相对于用shell的脚本来实现自动化&#x…

18.Nacos配置管理-微服务读取Nacos中的配置

需要解决的问题 1.实现配置更改热更新&#xff0c;而不是改动了配置文件还要去重启服务才能生效。 2.对多个微服务的配置文件统一集中管理。而不是需要对每个微服务逐一去修改配置文件&#xff0c;特别是公共通用的配置。 配置管理服务中的配置发生改变后&#xff0c;回去立…

主成分分析(PCA):揭秘数据的隐藏结构

在数据分析的世界里&#xff0c;我们经常面临着处理高维数据的挑战。随着维度的增加&#xff0c;数据处理、可视化以及解释的难度也随之增加&#xff0c;这就是所谓的“维度的诅咒”。主成分分析&#xff08;PCA&#xff09;是一种强大的统计工具&#xff0c;用于减少数据的维度…

python爬虫插件XPath的安装

概要 XPath Helper是一款专用于chrome内核浏览器的实用型爬虫网页解析工具。XPath可以轻松快捷地找到目标信息对应的Xpath节点&#xff0c;获取xpath规则&#xff0c;并提取目标信息&#xff0c;并进行校对测试&#xff1b;可对查询出的xpath进行编辑&#xff0c;正确编辑的结…

计算机网络和因特网

Internet: 主机/端系统&#xff08;end System / host&#xff09;&#xff1a; 硬件 操作系统 网络应用程序 通信链路&#xff1a; 光纤、网络电缆、无线电、卫星 传输效率&#xff1a;带宽&#xff08;bps&#xff09; 分组交换设备&#xff1a;转达分组 包括&#…
最新文章