网站首页 > 技术文章 正文
前言
如今大前端的趋势下,你停下学习的脚步了吗?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 文件的思路串行下来的。
- 上一篇: 前端路由原理 前端路由的优点和缺点
- 下一篇: 让你彻底搞懂前端路由前世今生! 前端路由的两种模式
猜你喜欢
- 2024-09-30 Vue Router 4 路由地址详解 vue router路由配置
- 2024-09-30 「vue基础」一篇浅显易懂的 Vue 路由使用指南( Vue Router 下)
- 2024-09-30 Vue Router 4 路由操作 - 路由导航
- 2024-09-30 为什么用vue.js,为什么前端开发46%的人都在用?
- 2024-09-30 vue-router 基础:4类路由跳转示例
- 2024-09-30 Vue Router 4 动态添加路由详解 vue router动态路由配置
- 2024-09-30 Vue进阶篇-Vue Router官方路由管理器
- 2024-09-30 循序渐进Vue+Element前端应用开发(3)—动态菜单和路由的关联处理
- 2024-09-30 哈希方式实现前端路由,核心是监听哈希事件hashchange
- 2024-09-30 前端开发框架VUE之路由vue-router
你 发表评论:
欢迎- 05-10如何优化数据库和前端之间的交互?
- 05-10前端代码优化小秘籍(前端优化24条建议)
- 05-10VS Code当中的15个神仙插件,值得收藏
- 05-10如何自己开发一个Google浏览器插件?
- 05-10前端流行框架Vue3教程:14. 组件传递Props效验
- 05-10吃了一年的SU,最好用的插件都在这了
- 05-10前端必看!这款神器让网站界面告别千篇一律
- 05-10程序员请收好:10个非常有用的 Visual Studio Code 插件
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端md5加密 (49)
- 前端路由 (55)
- 前端数组 (65)
- 前端定时器 (47)
- 前端懒加载 (45)
- 前端接口 (46)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle查询数据库 (45)
- oracle约束 (46)
- oracle 中文 (51)
- oracle链接 (47)
- oracle的函数 (57)
- mac oracle (47)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)