深入分析Vue-Router原理,彻底看穿前端路由

深入分析Vue-Router原理,彻底看穿前端路由beforeCreate 这个钩子代表生命周期创建之前,一般情况下是给组件增加一些特定的属性的时候才会使用的,在业务逻辑中基本上是使用不到的。

大家好,欢迎来到IT知识分享网。

前言

如今大前端的趋势下,你停下学习的脚步了吗?Vue3.0都Beta了,但是还是感觉有些知识点云里雾里的,小编研究了一下 Vue-Router 源码整理和总结了一些东西,看尤大大怎么设计的。希望能够对你们有所帮助,如果喜欢的话,可以帮忙点个赞:point_right:。

阅读本文之前,小编有三句话要说:

1.下面因为源码可能会变,所以没有贴源码,源码可以根据文章链接去github上下载

2.本文的基本思路是根据源码的 index.js 文件走的

安装

npm install vue-router 复制代码

使用方法见 官网

正文

1. install.js源码

源码地址: github.com/vuejs/vue-r…

1.1源码解析

首先在解析之前不得不说尤大大的细节做的是真好:+1:,第一行代码首先做了防止 VueRouter 的重复注册。

export function install (Vue) { if (install.installed && _Vue === Vue) return install.installed = true _Vue = Vue } 复制代码

接着使用了 Vue.mixin 混入的方法注册组件,使用了 beforeCreate 和 destoryed 两个钩子。

Vue.mixin({ beforeCreate () { //生命周期创建之前,一般情况是给组件增加一些特定的属性的时候使用这个钩子,在业务逻辑中基本上使用不到 if (isDef(this.$options.router)) { //isDef判断是否存在 this._routerRoot = this //this是根Vue实例 this._router = this.$options.router //把根实例上的router属性挂载到_router this._router.init(this) //调用init初始化路由的方法 //defineReactive数据劫持,一旦`this._router.history.current`值发生变化,更新_route Vue.util.defineReactive(this, '_route', this._router.history.current) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this //向上它的父亲一直向上找解决根组件嵌套问题 } registerInstance(this, this) //注册实例 }, destroyed () { registerInstance(this) //销毁实例 } }) 复制代码

beforeCreate 这个钩子代表生命周期创建之前,一般情况下是给组件增加一些特定的属性的时候才会使用的,在业务逻辑中基本上是使用不到的。在 beforeCreate 钩子中做了很重要的一步,判断根Vue实例上是否配置了 router ,也就是我们经常用 main.js 中的路由的注册。

import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ router, //:kissing_closed_eyes:就是这个地方:heart_eyes: render: h => h(App), }).$mount('#app') 复制代码

如果没有配置会向他的父级查找,保证每一个节点上都有 _routerRoot 属性,解决根组件的 嵌套 问题,如果没有 this._routerRoot = (this.$parent && this.$parent._routerRoot) || this 这一行代码,我们子组件上没有 __routerRoot 属性。

Vue.util.defineReactive(this, '_route', this._router.history.current) 复制代码

defineReactive 这个方法是Vue中的核心方法之一,即响应式原理。一旦 this._router.history.current 值发生变化,更新 _route 。那么如果页面的路由改变是怎么改变 _route 的呢?在 index.js 的 init 方法里:

history.listen(route => { //发布订阅模式每个 router 对象可能和多个 vue 实例对象(这里叫作 app)关联,每次路由改变会通知所有的实例对象。 this.apps.forEach(app => { app._route = route }) }) 复制代码

registerInstance(this, this) 这个函数怎么理解呢?我认为就是 router-view 的注册函数, _parentVnode 是实例的虚拟父级节点,需要找到父级节点中的 router-view 。首先会去判断是否存在父子关系节点,根据节点的层级在 route 的 matched 的属性上找到对应的数据之后,如果组件的路径 component 或者路由 route.matched 没有匹配渲染会 render 一个 h() ,那么 data 上面就不会添加 registerRouteInstance 注册路由的函数;

const matched = route.matched[depth] const component = matched && matched.components[name] // render empty node if no matched route or no config component if (!matched || !component) { cache[name] = null return h() } 复制代码
registerInstance(this, this) 复制代码
const registerInstance = (vm, callVal) => { let i = vm.$options._parentVnode if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal) } } 复制代码

registerInstance 这个方法在 beforeCreate 和 destroyed 的时候都被调用了一次,如果 val 值是 undefined 那么这个路由实例就会被注销,即 matched.instances[name] = undefined

data.registerRouteInstance = (vm, val) => { // val could be undefined for unregistration val可能没有定义被注销 const current = matched.instances[name] if ( (val && current !== vm) || (!val && current === vm) ) { matched.instances[name] = val } } 复制代码

这两个方法是利用 Object.defineProperty 的 get 方法给 vue 原型上添加 $router 和 $route 属性,这样就和上面提到的 保证每一个节点上都有_routerRoot属性 相呼应,如果没有 _routerRoot ,这里的添加属性会报错。

//vue原型上添加$router属性 Object.defineProperty(Vue.prototype, '$router', { get () { return this._routerRoot._router } }) //vue原型上添加$route属性 Object.defineProperty(Vue.prototype, '$route', { get () { return this._routerRoot._route } }) 复制代码

这里有个面试题: $route 和 $router 的区别:

  • $route 是一个对象
const route: Route = { name: location.name || (record && record.name), meta: (record && record.meta) || {}, path: location.path || '/', hash: location.hash || '', query, params: location.params || {}, fullPath: getFullPath(location, stringifyQuery), matched: record ? formatMatch(record) : [] } 复制代码
深入分析Vue-Router原理,彻底看穿前端路由

  • $router 就是 VueRouter 的实例
深入分析Vue-Router原理,彻底看穿前端路由

注册 RouterView 和 RouterLink 组件。

Vue.component('RouterView', View) //router-view组件 Vue.component('RouterLink', Link) //router-link组件 复制代码

view 和 link 两个组件都是 函数组件

1.2总结

在 install.js 中主要做了如下几件事:

1、绑定父子节点路由的关系

2、路由导航改变响应式的原理

3、将组件的实例和路由的规则绑定到一起

4、注册全局的 $route 和 $router 方法

5、注册 router-link 和 router-view 组件

2. view.js源码

源码地址: github.com/vuejs/vue-r…

2.1源码解析

函数组件中主要包含了 props 和 render 两部分。

props 中配置项 name 默认是 default 与之对应的就是路由的 命名视图 部分

props: { name: { type: String, default: 'default' } }, 复制代码

render 部分对应两个参数 _ , {props, children, parent, data} ,其中 _ 对应的是 createElement 方法, {props, children, parent, data} 对应的是 context ,即:

props children parent data 

通过当前路由地址所属的层级,找到在 matched 的位置,进行对应的渲染,如果的找不到不进行渲染。如果是父节点找到 keepAlive 的状态,之前加载过的直接使用直接的缓存,如果没有渲染一个空页面。

2.2总结

在 view.js 中主要是做了如下几件事:

1、一直向父级查找,找到当前路由所属的层级,找到对应的 router-view 进行渲染。

2、判断 keepAlive 的状态决定如何渲染。

3.link.js源码

源码地址: github.com/vuejs/vue-r…

3.1 源码解析

与 router-view 一样 router-link 也是一个函数组件,其中 tag 默认会被渲染成一个 a 标签.

props: { to: { type: toTypes, required: true }, tag: { type: String, default: 'a' }, exact: Boolean, append: Boolean, replace: Boolean, activeClass: String, exactActiveClass: String, ariaCurrentValue: { type: String, default: 'page' }, event: { type: eventTypes, default: 'click' } }, 复制代码

通过这些参数的配置调用render()方法中的 h(this.tag, data, this.$slots.default) 渲染 vnode ,即 <tag data >{this.$slots.default}</tag>

4.create-matcher.js源码

源码地址: github.com/vuejs/vue-r…

4.1源码解析

export function createMatcher ( routes: Array<RouteConfig>, //router中的routes router: VueRouter //router的配置 ): 复制代码

createMatcher 方法利用 createRouteMap 这个方法去格式化路由,而 createRouteMap 这个方法最终返回3个参数 pathList , pathMap , nameMap ,同时通过遍历和递归调用 addRouteRecord 方法对一系列的属性(包括 name , path , children , props , 路径正则 , 匹配规则是否开启大小写 等)进行判断和格式化之后返回需要的数据格式。

pathList: Array<string>, //列表 pathMap: Dictionary<RouteRecord>, //字典 nameMap: Dictionary<RouteRecord> //字典 复制代码
深入分析Vue-Router原理,彻底看穿前端路由

拿到这些数据之后,返回了两个方法 addRoutes 和 match

4.2 总结

1. create-matcher.js 主要的作用是拿到处理好的数据格式之后,导出两个核心方法

2. create-route-map.js 主要的作用是处理数据的格式。

5.路由模式源码

源码地址: github.com/vuejs/vue-r…

5.1源码解析

源码的结构是这样的:

首先定义了 History 类, HashHistory 、 HTML5History 、 AbstractHistory 都是继承 History 。

1、 hash 对应的是 HashHistory ,这个类里面主要的核心方法是 setupListeners 通过判断浏览器或者手机是否支持 supportsPushState 即 window.history.pushState 属性。如果不懂 pushState 可以阅读我的一篇文章 <一文带你真正了解histroy> 。如果支持监听 popstate 事件,如果不支持监听 hashchange 事件,在你采用浏览器前进后退时或者触发 go() 等事件来触发 popstate 。在监听之后采用发布订阅模式有一个事件移除机制,很细节哦。如果不支持 supportsPushState 使用 window.location.hash 或者 window.location.replace||assgin 。最后通过调用 base.js 中的基础类中的 transitionTo 方法通过 this.router.match 匹配到路由之后,通知路由的更新.

history.listen(route => { //发布订阅模式 this.apps.forEach(app => { app._route = route //$route的改变 }) }) 复制代码

2、 history 对应的是 HTML5History ,这个类里面主要的核心方法是 setupListeners 监听了 popstate 事件。

3、 abstract 对应的是 AbstractHistory ,这个类主要的核心声明了一个列表,判断列表里有没有这个路由或者下标,然后直接通知路由的更新。

5.2总结

路由模式 主要做了如下几件事:

1、通过对路由模式的不同监听不同的事件, hash 监听 popstate 和 hashchange 事件; history 监听 popstate 事件

2、通用 transitionTo 方法去更新路由信息。

补充知识: 判断数据类型的四种方法: typeof instanceof constructor Object.prototype.toString.call

结尾

上面内容是通过 index.js 文件的思路串行下来的。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/53014.html

(0)
上一篇 2024-08-05 09:45
下一篇 2024-08-11 13:26

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信