手写路由Vue-Router源码实现原理

1.Hash模式

  • hash就是url中#后面的部分
  • hash改变时,页面不会从新加载,会触发hashchange事件,去监听hash改变,而且也会被记录到浏览器历史记录中
  • vue-router的hash模式,主要是通过hashchange事件,根据hash值找到对应的组件去进行渲染(源码里会先判断浏览器支不支持popstate事件,如果支持,则是通过监听popstate事件,如果不支持,则监听hashchange事件)

hash模式页面跳转不刷新

根据http协议所示,url中hash改变请求是不会发送到服务端的,不管怎么location跳转,或者url上直接加上hash值回车,他都一样,请求是不会发送到服务端。
但是我们的系统里又引入了vue-router,其中hashchange这个事件监听到了hash的变化,从而触发了组件的更新,也就绘制出了相应的页面

2.History模式

  • 通过history.pushstate去修改页面的地址
  • 当history改变时,会触发popstate事件,所以可以通过监听popstate事件获取路由地址
  • 根据当前路由地址找到对应组件渲染

history模式,切换路由时页面刷新

看一下正确的history模式下,首页刷新到显示的整体流程:

1.将这个完整的url发到服务器nginx2.ngix需要配置用这个uri在指给前端index.html(因为根本没有任何一个服务器提供了这个url路由,如果直接访问的话就是404,所以就要指回给前端,让前端自己去根据path来显示)
location / {root   /usr/share/nginx/html/store;//项目存放的地址index  index.html index.htm;try_files $uri $uri/ /index.html;//history模式下,需要配置它
}所以try_files $uri $uri/的意思就是,比如http://test.com/example先去查找单个文件example,如果example不存在,则去查找同名的文件目录/example/,如果再不存在,将进行重定向index.html(只有最后一个参数可以引起一个内部重定向)凡是404的路由,都会被重定向到index.html,这样就显示正确了3.此时nginx将这个请求指回了前端的index.html,index.html中开始加载js,js中已有vue-router的代码,vue-router自动触发了popstate这个事件,在这个事件回调中,绘制了这个path下对应的页面组件

3.实现vue-router

VueRouter需要做以下这些事情

  1. 实现VueRouter根据不同模式进行不同处理
  2. 根据传入的路由配置,生成对应的路由映射
  3. init函数监听hashchange或popState事件,浏览器记录改变时重新渲染router-view组件
  4. 实现install静态方法
  5. 给Vue实例挂载router实例
  6. 注册全局组件和, router-view组件通过当前url找到对应组件进行渲染,并且url改变时,重新渲染组件,router-link则渲染为a标签
  7. 使用Object.defineProperty在Vue的原型上定义$router$route属性

代码实现

首先在创建文件 router/index.js,router/my-router.js

在index中我就不做多讲解了,和平常vue-router一样的配置,只是不需要vue-router,我们自己实现

一下都会有详细的注释,每一项的作用

import Vue from 'vue'
import VueRouter from './my-router';  //实现router文件
import HomeView from '../views/HomeView.vue'  //home文件
import about from '../views/AboutView.vue' //about文件Vue.use(VueRouter) //注意! 这是我们自己实现的文件,只是名字叫vuerouterconst routes = [   //这是我们的路由表{path: '/home',name: 'home',component: HomeView},{path: '/about',name: 'about',// route level code-splitting// this generates a separate chunk (about.[hash].js) for this route// which is lazy-loaded when the route is visited.component: about}
]const router = new VueRouter({  //创建实例mode: 'history', //模式 hash historyroutes //路由表给实例传过去
})export default router  //最后到处router

接下来就是正式实现router

首先在my-router最顶部,声明一个变量Vue并将其初始化为null

//my-router.js
let Vue = null; //保存Vue的构造函数,在插件中要使用,保留在将来为Vue分配一个值,减少全局命名空间的污染

定义一个HistoryRoute的类,并在其构造函数中将this.current也初始化为null

用途:

  • 状态管理this.current用于存储当前路由的状态或信息,比如当前激活的路由路径、参数等。
  • 初始化:通过将其初始化为null,你可以在类的其他方法中根据需要更新this.current的值,而不会受到未初始化属性的影响。
  • 灵活性:将this.current初始化为null提供了灵活性,允许你在类的生命周期中的任何时刻为其分配一个具体的值。
//my-router.js
let Vue = null;  //保存Vue的构造函数,在插件中要使用,保留在将来为Vue分配一个值,减少全局命名空间的污染
class HistoryRoute {constructor() {this.current = null;}
}

定义一个HistoryRoute的类

构造函数

  • constructor(options):接收一个options对象,该对象包含两个属性:moderoutesmode指定路由模式(hashhistory),routes是一个路由配置数组,每个路由配置对象包含pathcomponent属性。
  • this.changeMap:将routes数组转换为一个对象(Map),以path为键,component为值,方便后续根据路径快速查找对应的组件。
  • Vue.util.defineReactive(this, "history", new HistoryRoute());this.history = new HistoryRoute();:这里设置了history属性,后者覆盖了前者。HistoryRoute类用于管理当前路由状态。
class VueRouter {// 可以看到,暂时传入了两个,一个是mode,还有一个是routes数组。因此,我们可以这样实现构造器constructor(options) {this.mode = options.mode || "hash"; //默认是hashthis.routes = options.routes || [];  //默认为空// 由于直接处理数组比较不方便,所以我们做一次转换,采用path为key,component为value的方式this.routesMap = this.changeMap(this.routes);// 我们还需要在vue-router的实例中保存当前路径(在包含一些例如params信息,其实就是$route),所以我们为了方便管理,使用一个对象来表示:Vue.util.defineReactive(this, "history", new HistoryRoute());this.history = new HistoryRoute();}changeMap(routes) {// 使用render函数我们可以用js语言来构建DOMreturn routes.reduce((pre, next) => {console.log(pre);pre[next.path] = next.component;console.log(pre);return pre;}, {});}
}

添加init 方法:

  • 根据mode的不同,为window添加相应的事件监听器,以监听路由变化(hashchangepopstate事件),并更新history.current属性。
  • 在页面加载时(load事件),也根据当前URL设置history.current
class VueRouter {// 可以看到,暂时传入了两个,一个是mode,还有一个是routes数组。因此,我们可以这样实现构造器constructor(options) {this.mode = options.mode || "hash";this.routes = options.routes || [];// 由于直接处理数组比较不方便,所以我们做一次转换,采用path为key,component为value的方式this.routesMap = this.changeMap(this.routes);// 我们还需要在vue-router的实例中保存当前路径(在包含一些例如params信息,其实就是$route),所以我们为了方便管理,使用一个对象来表示:Vue.util.defineReactive(this, "history", new HistoryRoute());this.history = new HistoryRoute();this.init();}init() {// 如果是hash模式if (this.mode === "hash") {location.hash ? void 0 : (location.hash = "/");window.addEventListener("load", () => {this.history.current = location.hash.slice(1);});window.addEventListener("hashchange", () => {console.log(location.hash.slice(1))this.history.current = location.hash.slice(1);});}// 如果是history模式if (this.mode === "history") {location.pathname ? void 0 : (location.pathname = "/");window.addEventListener("load", () => {console.log(location.pathname)this.history.current = location.pathname;});window.addEventListener("popstate", () => {console.log(location.pathname)this.history.current = location.pathname;});}}changeMap(routes) {// 使用render函数我们可以用js语言来构建DOMreturn routes.reduce((pre, next) => {console.log(pre);pre[next.path] = next.component;console.log(pre);return pre;}, {});}

Vue Router作为一个Vue插件,需要创建instll方法

  1. 设置Vue实例:
    • Vue = v;:将传入的Vue实例赋值给全局变量Vue,以便后续使用。
  2. 全局混入:
    • 使用Vue.mixin在Vue的生命周期钩子beforeCreate中注入代码,用于处理路由相关的初始化。
    • 在根组件中,保存_router_root属性,分别指向VueRouter实例和根组件自身。
    • 对于非根组件,通过$parent找到根组件,从而访问到_router_root
VueRouter.install = (v) => {Vue = v;// vue-router还自带了两个组件,分别是router-link和router-view  在Vue.use(VueRouter)的时候加载的 所以我们要写在install里面// 新增代码Vue.mixin({beforeCreate() {// 如果是根组件if (this.$options && this.$options.router) {console.log(this.$options)// 将根组件挂载到_root上this._root = this;this._router = this.$options.router;// 拦截router-linkthis._router.mode === "history" && document.addEventListener("click", (e) => {if (e.target.className === "router-link-to") {// 阻止默认跳转事件e.preventDefault();// 手动改变url路径console.log(e.target.getAttribute("href"))history.pushState(null, "", e.target.getAttribute("href"));// 为current赋值url路径this._router.history.current = location.pathname;}});} else {// 如果是子组件// 将根组件挂载到子组件的_root上this._root = this.$parent && this.$parent._root;console.log(this._root);}},});
};
  • 使用Object.defineProperty在Vue的原型上定义$router$route属性,以便在任何Vue组件中通过this.$routerthis.$route访问到VueRouter实例和当前路由信息。
  • 在install方法里面写就行
 // 定义$routerObject.defineProperty(Vue.prototype, "$router", {get() {console.log(this);return this._root._router;},});// 定义$routeObject.defineProperty(Vue.prototype, "$route", {get() {return this._root._router.history.current;},});

定义全局组件(install方法里面添加)

  • router-link:一个用于导航的<a>标签组件,根据路由模式(hashhistory)自动添加#或正常路径。点击时,如果是history模式,会阻止默认跳转行为,改为手动更新URL和路由状态。
  • router-view:一个用于渲染当前路由对应组件的占位符组件。它根据history.currentroutesMap找到对应的组件,并使用Vue的render函数渲染。
  Vue.component("router-link", {props: {to: String,},render(h) {const mode = this._root._router.mode;let to = mode === "hash" ? "#" + this.to : this.to;return h("a",{attrs: {href: to,},// 新增代码class: "router-link-to",},this.$slots.default);},});Vue.component("router-view", {render(h) {const current = this._root._router.history.current;const routesMap = this._root._router.routesMap;return h(routesMap[current]);},});

完整代码

/** @Author: hukai huzhengen@gmail.com* @Date: 2023-06-08 17:49:08* @LastEditors: hukai huzhengen@gmail.com* @LastEditTime: 2024-10-24 11:08:00* @FilePath: \vue源码\vue-router-yuanma\src\router\my-router.js* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
let Vue = null;
class HistoryRoute {constructor() {this.current = null;}
}
// 因为router时new出来的 并且穿了一个对象 配置的路由由此可知router是一个class类
class VueRouter {// 可以看到,暂时传入了两个,一个是mode,还有一个是routes数组。因此,我们可以这样实现构造器constructor(options) {this.mode = options.mode || "hash";this.routes = options.routes || [];// 由于直接处理数组比较不方便,所以我们做一次转换,采用path为key,component为value的方式this.routesMap = this.changeMap(this.routes);// 我们还需要在vue-router的实例中保存当前路径(在包含一些例如params信息,其实就是$route),所以我们为了方便管理,使用一个对象来表示:Vue.util.defineReactive(this, "history", new HistoryRoute());this.history = new HistoryRoute();this.init();}init() {// 如果是hash模式if (this.mode === "hash") {location.hash ? void 0 : (location.hash = "/");window.addEventListener("load", () => {this.history.current = location.hash.slice(1);});window.addEventListener("hashchange", () => {console.log(location.hash.slice(1))this.history.current = location.hash.slice(1);});}// 如果是history模式if (this.mode === "history") {location.pathname ? void 0 : (location.pathname = "/");window.addEventListener("load", () => {console.log(location.pathname)this.history.current = location.pathname;});window.addEventListener("popstate", () => {console.log(location.pathname)this.history.current = location.pathname;});}}changeMap(routes) {// 使用render函数我们可以用js语言来构建DOMreturn routes.reduce((pre, next) => {console.log(pre);pre[next.path] = next.component;console.log(pre);return pre;}, {});}
}// 通过Vue.use 知道里面有一个install方法 并且第一个参数是Vue实例
VueRouter.install = (v) => {Vue = v;// vue-router还自带了两个组件,分别是router-link和router-view  在Vue.use(VueRouter)的时候加载的 所以我们要写在install里面// 新增代码Vue.mixin({beforeCreate() {// 如果是根组件if (this.$options && this.$options.router) {console.log(this.$options)// 将根组件挂载到_root上this._root = this;this._router = this.$options.router;// 拦截router-linkthis._router.mode === "history" && document.addEventListener("click", (e) => {if (e.target.className === "router-link-to") {// 阻止默认跳转事件e.preventDefault();// 手动改变url路径console.log(e.target.getAttribute("href"))history.pushState(null, "", e.target.getAttribute("href"));// 为current赋值url路径this._router.history.current = location.pathname;}});} else {// 如果是子组件// 将根组件挂载到子组件的_root上this._root = this.$parent && this.$parent._root;console.log(this._root);}},});// 定义$routerObject.defineProperty(Vue.prototype, "$router", {get() {console.log(this);return this._root._router;},});// 定义$routeObject.defineProperty(Vue.prototype, "$route", {get() {return this._root._router.history.current;},});Vue.component("router-link", {props: {to: String,},render(h) {const mode = this._root._router.mode;let to = mode === "hash" ? "#" + this.to : this.to;return h("a",{attrs: {href: to,},// 新增代码class: "router-link-to",},this.$slots.default);},});Vue.component("router-view", {render(h) {const current = this._root._router.history.current;const routesMap = this._root._router.routesMap;return h(routesMap[current]);},});
};
export default VueRouter;

最后在main.js中注册一下就行

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'Vue.config.productionTip = false
new Vue({router,store,render: h => h(App)
}).$mount('#app')

APP.vue中

<template><div id="app"><nav><router-link to="/home">Home</router-link> |<router-link to="/about">About</router-link></nav><router-view/></div>
</template>
<script>
export default {name:"Router",mounted(){console.log(this.$router)}
}
</script>
<style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;
}nav {padding: 30px;
}nav a {font-weight: bold;color: #2c3e50;
}nav a.router-link-exact-active {color: #42b983;
}
</style>

总结

这段代码通过定义VueRouter类和install方法,实现了一个简化版的Vue Router。它允许开发者定义路由规则,并在Vue应用中通过和组件实现页面导航和组件渲染。尽管这个实现相对简单,但它展示了Vue Router的核心概念和工作原理。

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

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

相关文章

linux shell 脚本语言教程(超详细!)

Shell 编程详细指南 什么是 Shell&#xff1f; Shell 是用户与操作系统内核之间的接口&#xff0c;允许用户通过命令行输入来控制操作系统。它充当命令解释器&#xff0c;读取用户输入的命令并执行相应的操作。Shell 提供了强大的脚本编程能力&#xff0c;可以自动化许多任务…

【javax maven项目缺少_Maven的依赖管理 引入依赖】

javax maven项目缺少_Maven的依赖管理 引入依赖 Maven的依赖管理 - 引入依赖依赖管理(引入依赖)导入依赖 https://blog.csdn.net/weixin_28932089/article/details/112381468 Maven的依赖管理 - 引入依赖 依赖管理(引入依赖) 能够掌握依赖引入的配置方式 导入依赖 导入依赖练…

银行客户贷款行为数据挖掘与分析

#1024程序员节 | 征文# 在新时代下&#xff0c;消费者的需求结构、内容与方式发生巨大改变&#xff0c;企业要想获取更多竞争优势&#xff0c;需要借助大数据技术持续创新。本文分析了传统商业银行面临的挑战&#xff0c;并基于knn、逻辑回归、人工神经网络三种算法&#xff0…

重构案例:将纯HTML/JS项目迁移到Webpack

我们已经了解了许多关于 Webpack 的知识&#xff0c;但要完全熟练掌握它并非易事。一个很好的学习方法是通过实际项目练习。当我们对 Webpack 的配置有了足够的理解后&#xff0c;就可以尝试重构一些项目。本次我选择了一个纯HTML/JS的PC项目进行重构&#xff0c;项目位于 GitH…

[旧日谈]高清画面撕裂问题考

背景 无边框透明背景透明的窗口&#xff0c;在随着缩放比例非整数倍数放大时的画面发生了露底、撕裂问题。 当我们在使用Qt开发的时候&#xff0c;遇到了一个结构性问题。因为我们的软件是自己做的&#xff0c;所以要自己定义标题栏&#xff0c;所以我们设置了软件为FrameLess…

mono源码交叉编译 linux arm arm64全过程

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

mysql——事务详解

一、事务定义 事务&#xff1a;事务是一个最小的不可在分的工作单元&#xff1b;通常一个事务对应一个完整的业务(例如银行账户转账业务&#xff0c;该业务是一个最小的工作单元)事务保证多条sql语句要么同时执行成功&#xff0c;要么同时执行失败一个完整的业务需要批量的DML…

移除Microsoft Edge浏览器“由你的组织管理“提示的方法

背景&#xff1a; 点击Microsoft Edge浏览器右上角的按钮有时候会出现提示“由你的组织管理”。但实际上自己的电脑并没有被公司或其他企业管理。 解决方案&#xff1a; 提示&#xff1a;修改注册表如果操作不当会影响电脑&#xff0c;请提前备份注册表&#xff01;&#xff…

Java 开发——(上篇)从零开始搭建后端基础项目 Spring Boot 3 + MybatisPlus

一、概述 记录时间 [2024-10-23] 本文是一个基于 Spring Boot 3 MybatisPlus 的项目实战开发&#xff0c;主要涵盖以下几个方面&#xff1a; 从零开始的项目创建IDEA 中开发环境的热部署Maven、Swagger3、MybatisPlus 等的配置路由映射知识静态资源访问文件上传功能实现拦截器…

Qt之QCamera的简单使用

文章目录 一、相机操作相关示例1.摄像头操作内容使用示例2.摄像头信息展示使用示例3.摄像头设置切换、预览操作示例 二、相机使用个人操作理解1.相机类支持信息获取2.相机类曝光、焦点、图像处理控制信息获取3.快速启动相机设置&#xff08;各个设备处于理想状态&#xff09; 三…

地平线x5下运行yolo11s-seg模型

经过地瓜机器人工作人员&#xff08;感谢吴超同学&#xff09;的及时技术支持&#xff0c;整体比较顺利的跑起来了yolo11s-seg分割模型。将一些经验记录下来&#xff1a; 首先下载使用docker镜像&#xff1a; https://developer.d-robotics.cc/forumDetail/228559182180396619 …

linux驱动—注册总线分析

成功地在直接注册了一个总线&#xff0c;并且在总线目录下创建了属性文件&#xff0c;什么会在 sys/bus 目录下生成 mybus,目录以及对应的 devices,drivers, drivers_autoprobe,drivers_probe&#xff0c;uevent目录和属性呢? /sys,目录下的目录都对应一个kobject&#xff0c;…

如何成为录屏高手?2024年全新录屏工具梳理,你选对了吗?

如何录屏&#xff1f;录屏现在对我们来说太重要了&#xff0c;不管是做教学视频、演示文稿&#xff0c;还是录游戏或者教别人怎么用软件&#xff0c;都离不开录屏工具。但是市面上录屏软件一大堆&#xff0c;挑个适合自己的真不容易。今天&#xff0c;我就来给你介绍几款特别火…

知识图谱解码:AI 如何构建知识网络

大家好&#xff0c;我是Shelly&#xff0c;一个专注于输出AI工具和科技前沿内容的AI应用教练&#xff0c;体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具&#xff0c;拥抱AI时代的到来。 AI工具集1&#xff1a;大厂AI工具【共23款…

凸轮应用实例(带进料装置的伺服压机控制)

凸轮表凸轮关系曲线建立 博途S7-1500T PLC曲柄连杆模型仿真(电子凸轮完整配置+SCL源代码)-CSDN博客文章浏览阅读4次。这篇博客介绍了曲柄连杆机构的位移与曲柄转动角度关系,通过MATLAB进行计算和Simulink验证,并提供博途SCL源代码。文章链接提供了详细的曲柄连杆数学模型分析…

分布式理论基础

文章目录 1、理论基础2、CAP定理1_一致性2_可用性3_分区容错性4_总结 3、BASE理论1_Basically Available&#xff08;基本可用&#xff09;2_Soft State&#xff08;软状态&#xff09;3_Eventually Consistent&#xff08;最终一致性&#xff09;4_总结 1、理论基础 在计算机…

WASM 使用说明23事(RUST实现)

文章目录 1. wasm是什么1.1 chatgpt定义如下:1.2 wasm关键特性&#xff1a; 2. wasm demo2.1 cargo 创建项目2.2 编写code2.3 安装wasm-pack2.4 编译 3.1 html页面引用wasm代码&#xff08;js引用&#xff09;3.2 访问页面4 导入js function4.1 编写lib.rs文件&#xff0c;内容…

【SpringCloud】06-Sentinel

1. 雪崩问题 一个微服务出现问题导致一系列微服务都不可以正常工作。 服务保护方案&#xff1a; 请求限流。线程隔离。 服务熔断 2. Sentinel 启动Sentinel java -Dserver.port8090 -Dcsp.sentinel.dashboard.serverlocalhost:8090 -Dproject.namesentinel-dashboard -ja…

【已解决】C# NPOI如何在Excel文本中增加下拉框

前言 上图&#xff01; 解决方法 直接上代码&#xff01;&#xff01;&#xff01;&#xff01;综合了各个大佬的自己修改了一下&#xff01;可以直接规定在任意单元格进行设置。 核心代码方法块 #region Excel增加下拉框/// <summary>/// 增加下拉框选项/// </s…

centeros7 编译ffmpeg

使用yum安装的路似乎已经堵住了&#xff0c;请求的镜像全是404或503 1.打开终端并使用yum安装EPEL存储库(Extra Packages for Enterprise Linux)&#xff1a;sudo yum install epel-release2.接下来&#xff0c;使用以下命令来安装FFmpeg&#xff1a;sudo yum install ffmpeg …