Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >从源码角度剖析vue-router

从源码角度剖析vue-router

原创
作者头像
conanma
修改于 2021-11-03 04:36:40
修改于 2021-11-03 04:36:40
5880
举报
文章被收录于专栏:正则正则

前言

Vue 是一个渐进式的框架,这意味着你可以只使用 Vue 的核心库来开发,但是当你在开发一个完整的业务项目时,路由是一个必不可少的部分

在曾经的前端领域中,一直都使用的是服务端渲染的模式,即用户输入 url 后,浏览器向服务器请求这个 url 对应的HTML,服务器返回 HTML给前端,前端再展示,然后当需要浏览别的页面时,需要点击 a 标签再向服务器发送一个请求,服务器就会再发给你目标页面的 HTML

这样会暴露一些缺点:

  • 每次跳转都向服务器请求,会增加服务器的压力
  • 每次跳转都会刷新页面导致跳转过程中会有一瞬间的白屏,用户体验不是非常好
  • 由于是服务端渲染,受到 XSS 的攻击可能性也较高

在 MVVM 框架兴起的同时,越来越多的开发者倾向于使用前端渲染的模式,服务端返回固定 JS 文件给前端,浏览器执行 JS 文件再渲染出整个页面,而在路由方面,前端会维护一个路由的层级树,当输入 url 后,不再向后端请求 HTML,而是去这个层级树中找到对应页面的 JS 文件并执行,从而渲染出新的页面,整个过程是纯前端控制的,所以也被称为前端路由

而 vue-router 作为 Vue 的路由库,它是怎么实现路由地址和组件之间的转换的呢,这篇文章中,我将会带大家深入 vue- router 的源码,解密 vue-router API 背后的原理

文中的源码截图只保留核心逻辑 完整源码地址

需要了解一些 Vue 的公共函数(mixins,install,defineReactive)

vue-router 版本:3.0.2

vue-router的使用方法

我们从 vue-router 的使用方法说起,当使用 vue-router 时,一般会分为3步

  1. 引入 vue-router,调用 Vue.use(Router)
  2. 实例化 router 对象,传入一个路由层级表 routes
  3. 在 main.js 中给根实例传入 router 对象

注册 vue-router 插件

当我们调用 Vue.use(Router)时会执行插件的注册流程

图1:

image

(删除了部分和入口无关的逻辑)

所有的 Vue 插件都会暴露一个 install 方法,当执行 Vue.use 时,实质上 Vue 会执行插件的 install 方法

混入全局钩子

了解过 Vue 响应式原理的朋友可以发现,vue-router 会通过 Vue.mixin 的方法全局混入 beforeCreate,destroyed 2个钩子,因为是全局混入的,所以之后所有的根实例和组件实例都会有这2个生命周期钩子

当根实例被实例化时,混入的 beforeCreate 第一次被执行,因为我们在 new Vue 时传入了 router 对象,它会被 Vue 作为 $options 的属性,所以会执行到 true 的逻辑,这里的核心在于 init 方法,它会初始化整个 vue-router 我们之后详解,另外将传入的 router 对象变成一个响应式对象,这个我们也之后讨论

除开根实例,其余所有的组件实例都会执行 false 的逻辑,它会给组件实例定义一个 _routerRoot 属性,因为 Vue 生成组件时是从上到下的,所以所有组件实例的 _routerRoot 属性都指向根实例

之后执行 registerInstance 这个也放到后面讨论

定义 $router,$route 属性

随后 Vue 在原型上定义了 $router,$route 2个对象,拦截 get 方法指向 _routerRoot.router,从上面一章可以发现,实质上指向的就是根实例的 router 对象,即日常开发中调用的 this.$router 最终都会指向根实例上的 router 对象

定义全局组件

最后通过 Vue.component 方法注册了2个全局组件,这样我们可以在任何地方直接使用<router-view>和<router-link>组件

实例化 vue-router

通常使用 vue-router 时,会在 router.js 中通过 new Router 的形式生成一个 router 的实例,并传入一个路由的层级表 routes 数组

图2:

随后我们找到源码中的 vue-router 类

图3:

image

整个 vue-router 实例化的过程核心就做了2件事

  1. 创建路由的映射表
  2. 根据传入的 mode 属性实例化不同的 history 路由实例

创建路由的映射表

图中第四行会执行到 createMatcher 方法,返回一个对象,包含 matchaddRoutes 这2个方法,这2个方法是 vue-router 中比较重要的函数,之后我们会分析它们的作用,在这之前先看一下 createMatcher 函数中的 createRouteMap 函数

图4:

image

createRouteMap 这个函数就是用来创建路由的映射表的,它是一个记录所有信息(路由记录)的对象,将传入的 routes 数组进行一系列处理,生成 pathList,pathMap,nameMap 3张路由映射表

图5:

image

createRouteMap 内部会遍历 routes 数组,执行 addRouteRecord 方法来为**每一个数组的每个元素(route 对象)创建记录,并储存在这3个路由映射表中

图6:

image

addRouteRecord 会将每个 route 对象转换为一个路由记录并保存在之前声明的3个路由映射表中,通过源代码发现,路由记录(record 对象)非常详细的记录了 route 对象的很多属性

  • path:路由的完整路径
  • regex:匹配到当前 route 对象的正则
  • components:route 对象的组件(因为 vue-router 中有命名视图,所以会默认放在 default 属性下,instances 同理)
  • instances: route 对象对应的 vm 实例
  • name:route 对象的名字
  • parent:route 对象的父级路由记录
  • matchAs:路由别名
  • redirect:路由重定向
  • beforeEnter:组件级别的路由钩子
  • meta:路由元信息
  • props:路由跳转时的传参

在创建路由记录前,会使用 normalizedPath 规范化 route 对象的路径,如果传入的 route 对象含有父级 route 对象,会将父级 route 对象的 path 拼上当前的 path

图7:

image

例如图2中的 comp1Child 这个 route 对象,它的 path 最终会变成

"/comp1" + "comp1Child" => "/comp1/com1Child"

而最终会生成的路由记录是这样的

图8:

随后因为 route 可能含有 children 属性,即含有子的 route 对象组成的数组,所以需要进行递归的遍历,然后将 record 对象放入这3个路由映射表中,而这3个路由映射表的区别在于

  • pathList:数组,保存了 route 对象的路径
  • pathMap:对象,保存了所有 route 对象对应的 record 对象
  • nameMap:对象,保存了所有含有name属性的 route 对象对应的 record 对象

图2中的路由对应的3张路由映射表如下:

pathList:

pathMap:

nameMap:

可以看到 pathMap 和 nameMap 是一样的,因为图2中的路由都有 name 属性,如果某个路由没有 name 属性,则只会在 pathMap 中存在

对比保存了所有 route 对象的 routes 数组和这3个路由映射表,我们可以发现:routes 对象是一个递归的树形结构,而路由映射表是一个扁平的一维结构,通过路由映射表里的 parent 属性来维护父子关系

动态添加路由的 addRoutes 函数

在创建完路由映射表后,会向外暴露一个动态添加路由的 API addRoutes

图10:

image

它的原理其实很简单,就是接受一个 route 对象,并且把它转换成 record 对象,然后合并到之前生成的路由映射表中,所以我们可以在外部调用 router.addRoutes 动态注册路由

返回 $route 对象的 match 函数

createMatcher 返回的第二个函数是 matchmatch 函数会返回一个 route 对象

图11:

image

之前说的 route 是针对 new Router 时传入的 routes 数组的每个元素,而 $route 是最终返回作为 Vue.prototype.$route 使用的对象,在 flow 语言中,route 的类型是 RouteConfig,而 $route 的类型是 Route,具体接口的定义可以查看源代码,虽然在源码中两者变量名都是 route,但我下文会使用 $route 来区分通过 this.$route 返回 route 对象

图12:

routes :

$route :

前者表示的是路由的一些基础配置项,而后者是真正经过 vue-router 处理后表示当前路由的对象

每次路由跳转的时候都会执行这个 match 函数生成一个 $route 对象,具体什么时候会触发 match 放到下篇中讲,这章先分析 match 函数是如何最终生成一个真正的 $route 对象的

生成 loaction 对象

match函数首先会执行 normalizeLocation 函数,它是一个辅助函数,会将调用 router.push / router.replace 时跳转的路由地址转为一个 location 对象

那什么是 location 对象? MDN 上是这么解释的

Location接口表示其链接到的对象的位置(URL)。所做的修改反映在与之相关的对象上。 DocumentWindow 接口都有这样一个链接的Location,分别通过 Document.locationWindow.location 访问。

通俗的来说就是用一个对象来描述当前 url 的一些信息。当我们在地址栏中输入 www.baidu.com ,按 F12 打开控制台,输入 loaction 就能展示出当前地址的一些信息

图13:

image

vue-router 在 location 接口的基础上做了一些增强,添加了 name,path,hash 等 vue-router 特有的属性

举个例子,当调用 router.push({name:"comp1"}) 使用 name 的形式进行路由跳转时,返回的 loaction 对象就会有一个 name 属性,当 name 存在时,会走到图11中的 true 逻辑,从之前 createMatcher 生成的 nameMap 路由映射表中找到对应 name 的路由记录 record 对象,最终会执行 _createRoute 这个方法

而调用 router.push("/comp1") 使用路径的形式进行路由跳转,同样也会返回一个 location 对象,但不会有 name 属性,走图11的 false 逻辑,从另外2个路由映射表 pathMap,pathList 中找到对应的路由记录,最终也会执行 _createRoute 这个方法

可见无论使用 name 跳转还是使用 path 跳转,最终都会执行 _createRoute ,带下划线的 _createRoute 是一个私有方法,它最终会调用 createRoute 生成 $route 对象

生成 $route 对象

图14:

image

经过对一些 query 参数的处理,最终返回 $route 对象,其中有一个 matched 属性值得注意,它通过 formatMatch 函数生成,查看过 this.$route 返回值的朋友应该知道,matched 是一个数组,每个元素都是一个路由记录(record)

图15:

image

还记得之前在生成路由记录的时定义的 parent 属性吗?它的其中一个用途就是通过不断的向上查找父级的路由记录,放入 matched 数组中,最终返回一个保存了当前路由记录和所有父级数组,顺序是 父 => 子

图16:

而这个 matched 数组最终会决定触发哪些路由组件的哪些路由守卫钩子,关于路由钩子部分我们放到下篇来说

生成 history 路由实例

再次回到图3,vue-router 根据传入参数的 mode 属性来实例化不同的路由类(HTML5,hash,abstract),这也是官方提供给开发者的3种不同的选择来生成路由

  • HTML5 路由是相对比较美观的一种路由,和正常的 url 显示没有什么区别,核心依靠 pushStatereplaceState 来实现不向后端发送请求的路由跳转,但是当用户点击刷新按钮时会存在找不到页面的情况,需要配合 nginx 来做一层转发
  • hash 路由是默认使用的路由,在 url 中会存在一个 # 号,核心依靠这个 # 号也就是曾经作为路由的锚点来实现不向后端发送请求的路由跳转
  • abstract 路由是一种抽象路由,一般用在非浏览器端,维护一种抽象的路由结构,使得能够嫁接在客户端或者服务端等没有 history 路由的地方

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
一篇关于 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>`标签-->
前端达人
2021/08/12
6560
超燃|从0到1手把手带你实现一款Vue-Router
无论是日常业务还是面试过程中,相信大家对于前端路由这个话题或多或少都有自己的应用和理解。
19组清风
2022/02/28
2.2K0
超燃|从0到1手把手带你实现一款Vue-Router
深入分析Vue-Router原理,彻底看穿前端路由
如今大前端的趋势下,你停下学习的脚步了吗?Vue3.0 都 Beta 了,但是还是感觉有些知识点云里雾里的,小编研究了一下Vue-Router源码整理和总结了一些东西,看尤大大怎么设计的。希望能够对你们有所帮助,如果喜欢的话,可以帮忙点个赞。
小丑同学
2020/09/21
2.1K0
vue-router源码分析
这是一篇集合了从如何查看 vue-router源码(v3.1.3),到 vue-router源码解析,以及扩展了相关涉及的知识点,科普了完整的导航解析流程图,一时读不完,建议收藏。
conanma
2021/11/03
1.1K0
跟着来,你也可以手写VueRouter
VueRouter,无疑是每个 Vue 开发者时时刻刻都在使用的东西了,但对于它的源码,你了解多少呢?
isboyjc
2022/03/28
1.6K0
跟着来,你也可以手写VueRouter
浅析 vue-router 源码和动态路由权限分配
? 这是第 72 篇不掺水的原创,想要了解更多,请戳上方蓝色字体:政采云前端团队 关注我们吧~ 本文首发于政采云前端团队博客:浅析 vue-router 源码和动态路由权限分配 https://ww
政采云前端团队
2020/10/26
4.7K0
浅析 vue-router 源码和动态路由权限分配
Vue路由History模式分析
Vue-router是Vue的核心组件,主要是作为Vue的路由管理器,Vue-router默认hash模式,通过引入Vue-router对象模块时配置mode属性可以启用history模式。
WindRunnerMax
2020/10/26
1.2K0
前端面试题锦集:第三期VueRouter
面试只是起点,能力才是终局。本期着重讨论vue-router。 router-view组件 我们平时写vue项目的时候,遇到路由的时候习惯上直接使用router-view组件,但是这个组件时谁提供
terrence386
2022/07/15
6280
前端面试题锦集:第三期VueRouter
如何吃透 vue-router
vue-router 是vue的插件,是对 vue的前端路由管理器,使用中通常分为hash 与 history模式。
前端小tips
2021/11/27
4580
如何吃透 vue-router
vue-router 源码阅读 - 文件结构与注册机制
前端路由是我们前端开发日常开发中经常碰到的概念,在下在日常使用中知其然也好奇着所以然,因此对 vue-router 的源码进行了一些阅读,也汲取了社区的一些文章优秀的思想,于本文记录总结作为自己思考的输出,本人水平有限,欢迎留言讨论~
前端下午茶
2019/06/27
9170
vue-router 源码阅读 - 文件结构与注册机制
vue-router 详解
vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。 我们可以访问其官方网站对其进行学习:https://router.vuejs.org/zh/
全栈程序员站长
2022/09/14
1.9K0
vue-router 详解
vue-router源码解读
简单来说,路由就是用来和后端服务器进行交互的一种方式,通过不同的路径,请求不同的资源,请求不同的页面是路由的其中一种功能。
Careteen
2022/02/14
1.2K0
Vue-Router 入门与提高实战示例
路由(routing)是指从源到目的地时,决定端到端路径的决策过程。 对于VueRouter而言,路由就是根据一个请求路径选中一个组件进行 渲染的决策过程:
笔阁
2018/09/04
3.6K0
Vue-Router 入门与提高实战示例
Vue-Router 简易实现
# 需求分析 作为一个插件存在:实现VueRouter类和install方法 实现两个全局组件:router-view用于显示匹配组件内容,router-link用于跳转 监控url变化:监听hashchange或popstate事件 响应最新url:创建一个响应式的属性current,当它改变时获取对应组件并显示 # 实现 # 作为一个插件存在 // cvue-router.js let Vue; // 1. 实现一个插件:挂载$router class CVueRouter { constructo
Cellinlab
2023/05/17
2120
【路由】:history——ReactRouter vs VueRouter
站在业务开发者角度,vue-router 用起来更舒服一些,因为 vue-router 提供的导航守卫、命名路由、路由传参等特性,基本上不需要再去二次封装,拿来就能用,实用性比较高。react-router 则更自由灵活一些,很多场景、模式,需要根据官方文档的建议,再结合实际业务场景,进行二次封装,才能应用到生产项目中,复杂度高一些。
WEBJ2EE
2021/04/07
1.8K0
vue-router的超神之路
本文是vue-router系列。这里从浏览器到vue-router原理到最佳实践都会有详细的讲解。由于篇幅较长,建议可以选择感兴趣的目录看。
用户4131414
2020/03/19
1.6K0
vue-router基本使用
 路由,其实就是指向的意思,当我点击页面上的home  按钮时,页面中就要显示home的内容,如果点击页面上的about 按钮,页面中就要显示about 的内容。Home按钮  => home 内容, about按钮 => about 内容,也可以说是一种映射。 所以在页面显示中,有两个部分,一个是需要点击的部分,一个是点击之后,显示点击内容的部分。    点击之后,怎么做到正确的对应,比如,我点击home 按钮,页面中怎么才能显示home的内容。这就要在js 文件中配置路由。   路由中有三个基本的概念
大当家
2018/06/28
9900
前端路由简介以及vue-router实现原理
简单来说路由就是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不同的资源,请求不同的页面是路由的其中一种功能。
muwoo
2018/06/06
1.6K1
前端路由简介以及vue-router实现原理
手写Vue-router核心原理,再也不怕面试官问我Vue-router原理
在 Web 前端单页应用 SPA(Single Page Application)中,路由描述的是 URL 与 UI 之间的映射关系,这种映射是单向的,即 URL 变化引起 UI 更新(无需刷新页面)。
秋风的笔记
2021/09/22
7.7K1
手写Vue-router核心原理,再也不怕面试官问我Vue-router原理
Vue-router 学习笔记
在以前,前后端是不分离的,这个阶段通常是由服务端渲染好页面(SSR),再发送页面给前端去展示;接着到了前后端分离的阶段,前端向静态资源服务器拿资源,再通过 js 渲染页面,此时仍然是一个 url 对应一份 html+css+js。再后来出现了 SPA 单页面应用的概念,实际上它只有一个页面,但给我们的体验是多页面之间的切换。
Chor
2019/11/18
6670
相关推荐
一篇关于 Vue-Router 路由模式的整理
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档