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 parametersconst 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 hooksstrats.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: () => voidstatic version: stringstatic isNavigationFailure: Functionstatic NavigationFailureType: anystatic START_LOCATION: Routeapp: anyapps: Array<any>ready: booleanreadyCbs: Array<Function>options: RouterOptionsmode: stringhistory: HashHistory | HTML5History | AbstractHistorymatcher: Matcherfallback: booleanbeforeHooks: 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 = nullthis.apps = []this.options = optionsthis.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'//判断模式是哈希还是historythis.fallback = mode === 'history' && !supportsPushState && options.fallback !== false // 判断浏览器是否支持history,如果不支持则回退到hash模式;if (this.fallback) {mode = 'hash'}if (!inBrowser) {mode = 'abstract'}this.mode = modeswitch (mode) {// 根据不同模式创建对应的history实例case 'history':this.history = new HTML5History(this, options.base)breakcase 'hash':this.history = new HashHistory(this, options.base, this.fallback)breakcase 'abstract':this.history = new AbstractHistory(this, options.base)breakdefault: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-linecreateRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent)// add aliases of parentif (parent && parent.alias.length) {createRouteMap(// $flow-disable-line route is defined if parent isparent.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 } = locationif (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.redirectlet redirect = typeof originalRedirect === 'function'? originalRedirect(createRoute(record, location, null, router)): originalRedirectif (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 = redirectconst { name, path } = relet { query, hash, params } = locationquery = re.hasOwnProperty('query') ? re.query : queryhash = re.hasOwnProperty('hash') ? re.hash : hashparams = re.hasOwnProperty('params') ? re.params : paramsif (name) {// resolved named directconst 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 redirectconst rawPath = resolveRecordPath(path, record)// 2. resolve paramsconst resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)// 3. rematch with existing query and hashreturn 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.matchedconst aliasedRecord = matched[matched.length - 1]location.params = aliasedMatch.paramsreturn _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 0params[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.mzph.cn/pingmian/3430.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Visual Studio Code 快捷键大全

文章目录 1. 全局快捷键2. 基本编辑3. 导航4. 查找 & 替换5. 多光标 & 选择6. 代码编辑7. 编辑器管理8. 文件管理9. 显示10. Debug 调试11. 终端&#xff08;Terminal&#xff09; 1. 全局快捷键 快捷键说明Ctrl Shift P&#xff0c;F1显示命令面板Ctrl P快速打开&am…

牛客NC199 字符串解码【中等 递归,栈的思想 C++/Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/4e008fd863bb4681b54fb438bb859b92 相同题目&#xff1a; https://www.lintcode.com/problem/575 思路 解法和基础计算器1&#xff0c;2,3类似,递归参考答案C struct Info {string str;int stopindex;Info(str…

AOC vs. DAC:哪个更适合您的网络需求?

在现代网络通信中&#xff0c;选择合适的连接线缆对于数据传输的稳定性和速度至关重要。两种常见的线缆类型是 AOC&#xff08;Active Optical Cable&#xff09; 和 DAC&#xff08;Direct Attach Cable&#xff09;。本文将详细介绍这两种线缆的特点、优势和适用场景&#xf…

Aigtek:介电弹性体高压放大器在软体机器人研究中的应用

近年来软体机器人的研究成为目前机器人研究领域的热点&#xff0c;由于软体材料的自由度可以根据需求自由变化&#xff0c;因此软体机器人有着极高的灵活性&#xff0c;而且软体机器人因其材料的柔软性有着很好的人机交互性能和安全性。它的出现成功解决了传统的刚性机器人人机…

链表-LRU缓存

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRU是Least Recently Used的缩写&#xff0c;意为“最近最少使用”。LRU是一种常用的缓存淘汰策略&#xff0c;用于管理缓存中的数据。 举个例子&#xff0c;你从一堆书中找出…

JavaScript云LIS系统概述 前端框架JQuery+EasyUI+Bootstrap医院云HIS系统源码 开箱即用

云LIS系统概述JavaScript前端框架JQueryEasyUIBootstrap医院云HIS系统源码 开箱即用 云LIS&#xff08;云实验室信息管理系统&#xff09;是一种结合了计算机网络化信息系统的技术&#xff0c;它无缝嵌入到云HIS&#xff08;医院信息系统&#xff09;中&#xff0c;用于连…

《异常检测——从经典算法到深度学习》27 可执行且可解释的在线服务系统中重复故障定位方法

《异常检测——从经典算法到深度学习》 0 概论1 基于隔离森林的异常检测算法 2 基于LOF的异常检测算法3 基于One-Class SVM的异常检测算法4 基于高斯概率密度异常检测算法5 Opprentice——异常检测经典算法最终篇6 基于重构概率的 VAE 异常检测7 基于条件VAE异常检测8 Donut: …

Oracle 监控 SQL 精选 (一)

Oracle数据库的监控通常涉及性能、空间、会话、对象、备份、安全等多个层面。 有效的监控可以帮助 DBA 及时发现和解决问题&#xff0c;提高数据库的稳定性和性能&#xff0c;保障企业的数据安全和业务连续性。 常用的监控指标有&#xff1a; 性能指标&#xff1a; 查询响应时间…

实验二: 密码恢复

1.实验环境 用路由器和交换机搭建实验环境 2.需求描述 实现管理员主机对交换机和路由器的远程管理 设备上配置的密码都要被加密 3.推荐步骤 对于路由器的步骤如下&#xff1a; 重启路器&#xff0c;并按下 CtrlBreak 组合键中断0S 的加载&#xff0c;路由器将进入ROM Mon…

AUTOSAR OS Alarm讲解

Alarm定义 使用中断ISR可以构建以不同速率激活任务的系统。然而,对于复杂的系统,这种方式效率不高且不切实际。alarm(警报)提供了一种更方便、更灵活的调度系统的方式。 alarm由两部分组成: 一个计数器一个或多个与计数器关联的alarmalarm指定当达到特定的计数器值时要执…

千行 MySQL 学习笔记总结大全,语法大全

Windows服务 -- 启动MySQLnet start mysql -- 创建Windows服务sc create mysql binPath mysqld_bin_path(注意&#xff1a;等号与值之间有空格) 连接与断开服务器 mysql -h 地址 -P 端口 -u 用户名 -p 密码SHOW PROCESSLIST -- 显示哪些线程正在运行 SHOW VARIABLES -- 显示…

抽象工厂模式(Redis 集群升级)

目录 定义 Redis 集群升级 模拟单机服务 RedisUtils 模拟集群 EGM 模拟集群 IIR 定义使⽤接⼝ 实现调⽤代码 代码实现 定义适配接⼝ 实现集群使⽤服务 EGMCacheAdapter IIRCacheAdapter 定义抽象⼯程代理类和实现 JDKProxy JDKInvocationHandler 测试验证 定义 …

Mockaroo - 在线生成测试用例利器

简介&#xff1a;Mockaroo 是一个无需安装的在线工具&#xff0c;用于生成大量的自定义测试数据。它支持多种数据格式&#xff0c;如JSON、CSV、SQL和Excel&#xff0c;并能模拟复杂的数据结构。 历史攻略&#xff1a; 测试用例&#xff1a;多条件下编写&#xff0c;懒人妙用…

ChatGPT付费创作系统V2.8.4独立版 WEB+H5+小程序端 (新增Pika视频+短信宝+DALL-E-3+Midjourney接口)

小狐狸GPT付费体验系统最新版系统是一款基于ThinkPHP框架开发的AI问答小程序&#xff0c;是基于国外很火的ChatGPT进行开发的Ai智能问答小程序。当前全民热议ChatGPT&#xff0c;流量超级大&#xff0c;引流不要太简单&#xff01;一键下单即可拥有自己的GPT&#xff01;无限多…

网盘——文件重命名

文件重命名具体步骤如下&#xff1a; 目录 1、具体步骤 2、代码实现 2.1、添加重命名文件的槽函数 2.2、关联重命名文件夹信号槽 2.3、添加重命名文件的协议 2.4、添加槽函数定义 2.5、服务器 2.6、添加重命名文件的case 2.7、客户端接收回复 3、测试 3.1、点击重命…

debian配置四叶草输入法

效果展示 一、前言 在linux下体验比较好的输入法只有两款&#xff1a;搜狗输入法、四叶草输入法。 ubuntu下可以成功配置搜狗输入法&#xff0c;但debian下从来没有成功过。 今天在用fcitx5 四叶草时发现VNC远程输入法会失灵&#xff0c;于是改用了ibus 四叶草&#xff0c…

Qt : 禁用控件默认的鼠标滚轮事件

最近在写一个模拟器&#xff0c;在item中添加了很多的控件&#xff0c;这些控件默认是支持鼠标滚动事件的。在数据量特别大的时候&#xff0c;及容易不小心就把数据给修改了而不自知。所有&#xff0c;我们这里需要禁用掉这些控件的鼠标滚轮事件。 实现的思想很简单&#xff0c…

原生微信小程序中案例--仿boss区域树选择列多选功能

1. 需求描述&#xff1a; 区域三级列表&#xff0c; 有添加&#xff0c;编辑&#xff0c;删除功能。 选择父级分类&#xff0c;其下子类全部选中&#xff0c;当前分类后加标志显示全字样取消选中子类&#xff0c;其父类分类后标志显示选中数量若子类全部选中&#xff0c;除当…

对2023年图灵奖揭晓看法

2023年图灵奖揭晓&#xff0c;你怎么看&#xff1f; 2023年图灵奖&#xff0c;最近刚刚颁给普林斯顿数学教授 Avi Wigderson&#xff01;作为理论计算机科学领域的领军人物&#xff0c;他对于理解计算中的随机性和伪随机性的作用&#xff0c;作出了开创性贡献。这些贡献不仅推…

【stomp 实战】spring websocket源码分析之握手请求的处理

上一节【搭建一套websocket推送平台】我们通过一个项目&#xff0c;实现了一套推送平台。由于spring框架对于websocket的支持和stomp协议的良好封装&#xff0c;我们很容易地就实现了websocket的消息推送功能。虽然搭建这么一套推送系统不难&#xff0c;但是如果不了解其底层原…