大家好,欢迎来到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) : [] } 复制代码
- $router 就是 VueRouter 的实例
注册 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> //字典 复制代码
拿到这些数据之后,返回了两个方法 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