简述vue-router实现原理
router源码解读
阅读请关注下代码注释
打个广告:哪位大佬教我下sf怎么排版啊,不会弄菜单二级导航(扑通.gif)
<h2>1. router是什么</h2>
首先,你会从源码里面引入Router,然后再传入参数实例化一个路由对象
// router/index.js
import Router from 'vue-router'
new Router({...})
...
源码基础类:
// 源码index.js
export default class VueRouter {...constructor (options: RouterOptions = {}) {this.app = nullthis.apps = []this.options = optionsthis.beforeHooks = []this.resolveHooks = []this.afterHooks = []this.matcher = createMatcher(options.routes || [], this)let mode = options.mode || 'hash' // 不选择模式会默认使用hash模式this.fallback = mode === 'history' && !supportsPushState && options.fallback !== falseif (this.fallback) {mode = 'hash'}if (!inBrowser) { // 非浏览器环境默认nodejs环境mode = 'abstract'}this.mode = modeswitch (mode) { // 根据参数选择三种模式的一种case 'history':this.history = new HTML5History(this, options.base) // 根据HTML5版History的方法和属性实现的模式breakcase 'hash':this.history = new HashHistory(this, options.base, this.fallback) // 利用url中的hash特性实现breakcase 'abstract':this.history = new AbstractHistory(this, options.base) // 这种模式原理暂不清楚breakdefault:if (process.env.NODE_ENV !== 'production') {assert(false, `invalid mode: ${mode}`)}}}...// 一些api方法,你应该很熟悉,$router.push(...)push (location: RawLocation, onComplete?: Function, onAbort?: Function) {this.history.push(location, onComplete, onAbort)}replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {this.history.replace(location, onComplete, onAbort)}go (n: number) {this.history.go(n)}back () {this.go(-1)}forward () {this.go(1)}...
}
我们创建的路由都是VueRouter类的实例化,用来管理我们的【key-components-view】,一个key(代码中的path)对应一个组件,view也就是<router-view>在template里面占个坑,用来根据key展示对应的组件,实例上的func让我们可以控制路由,也就是官网的api
说简单点,路由就是一个‘轮播图’,emmmmmm,说轮播好像也不过分哈,写个循环切换key的func就是‘轮播了’,而key就是轮播的index,手动滑稽。那么,vue-router是如何实现不发送请求就更新视图的呢,让我们来看看vue如何使用路由的
实例化后的路由输出:区分下route和router
2. router工作原理
如果你要使用到router,你会在实例化Vue的参数options中加入router
// main.js
improt xxx from xxx
import router from xxx
new Vue({el: '#app',router: router,components: { App },template: '<App/>'
})
那,Vue是如何使用这个参数呢,vue-router是作为插件加入使用的,通过mixin(混合)来影响每一个Vue实例化,在beforeCreate 钩子的时候就会完成router的初始化,从参数获取router -> 调用init初始化 -> 加入响应式(defineReactive方法在vue源码用的很多,也是底层实现响应式的核心方法)
// 源码install.js
Vue.mixin({beforeCreate () {if (isDef(this.$options.router)) {this._routerRoot = thisthis._router = this.$options.router // 获取options里面的router配置this._router.init(this) // 初始化,这个init是VueRouter类里面的方法,实例化后会继承这个方法,方法代码见下方 Vue.util.defineReactive(this, '_route', this._router.history.current) // 这个是把_route加入数据监控,所以你可以watch到_route} else {this._routerRoot = (this.$parent && this.$parent._routerRoot) || this}registerInstance(this, this)},destroyed () {registerInstance(this)}})
初始化会做些什么:
-判断主程序状态(已经初始化了的vue实例不会再重新初始化路由,也就是你不能手动去再init一次)
-把实例加入内置数组
-判断history的类型,做一些类似优化的事,比如hash模式的setupListeners方法,就是延迟监听hashChange事件,等到vue完成挂载再监听,太细节不用深入
-listen定义一个callback,listen是定义在最底层History类上的,作用就是定义一个callback,listen会在需要的时候被调用,在路由发生变化的时候会执行这个callback
// 源码index.js
export default class VueRouter {
...
init (app: any /* Vue component instance */) {process.env.NODE_ENV !== 'production' && assert(install.installed,`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +`before creating root instance.`)this.apps.push(app) // 这个apps存储了让所有的Vue实例化(根组件),后面遍历的时候,会把当前标记route挂到所有根组件的,也就是 vm._route 也是 vm._router.history.current// main app already initialized.if (this.app) {return}this.app = appconst history = this.historyif (history instanceof HTML5History) {history.transitionTo(history.getCurrentLocation())} else if (history instanceof HashHistory) {const setupHashListener = () => {history.setupListeners()}history.transitionTo(history.getCurrentLocation(),setupHashListener,setupHashListener)}history.listen(route => { // 注意这个listen会在后面用到this.apps.forEach((app) => {app._route = route // 根组件全部获取当前route})})}
...
}
关于route的变化过程会在下面具体模式中说明,这里先跳过,接下来先说vue拿到router后,怎么使用router来渲染组件的
3. vue如何使用router的
在安装vue-router插件的时候
export function install (Vue) {...Vue.component('RouterView', View) // <router-link> & <router-view> 你应该很熟悉,本质就是vue组件,看源码之前我的猜测也是组件Vue.component('RouterLink', Link)...
}
router-link你不一定会使用,但是router-view你肯定会使用,它就是作为'窗口'的存在,来渲染你需要展示的组件。
那,从这个组件开始说,一个前提条件是:vnode是通过render来创建的,也就是说改变_route的值会执行render函数,Router-View这个组件定义了自己的render,省略了大部分代码,这两行够了,你最终通过<router-view>看到的视图就是这么来的
// vue源码render.js
export function renderMixin (Vue: Class<Component>) {
...
vnode = render.call(vm._renderProxy, vm.$createElement)
...
}
// router源码 view.js
render (_, { props, children, parent, data }) {
...
const h = parent.$createElement
...
return h(component, data, children)
}
第一种:hashHistory模式
流程
$router.push() --> HashHistory.push() --> History.transitionTo() --> History.updateRoute() --> {app._route = route} --> vm.render()
1. 关于hash
url中#号后面的参数,别名哈希值,关于hash的一些特性
1.改变hash并不会引起页面重载
2.HTTP请求不包括#,所以使用hash不会影响到其他功能
3.改变#会改变浏览器的访问历史
4.window.location.hash可以读取哈希值
5.JavaScript可以通过onhashchange监听到hash值的变化,这就意味着可以知道用户在浏览器手动改变了hash值
因为这些特性才有的hashHistory
更多关于hash知识见 URL的井号 - 阮一峰的网络日志
2. hashHistory源码
首先,这三种模式都是通过继承一个基础类History来的
export class HashHistory extends History {
...
}
那,三种模式肯定有相同的属性,相同的方法,肯定不会去创建三次所以从一个基类继承,然后各自的部分属性or方法会有差异,至于History这个类,我是不会去细看的,反正我也看不懂,哈哈哈哈
router上的实例属性、方法可以在VueRouter、HashHistory/HTML5History/AbstractHistory、History上找到,这里说下HashHistory的几个func的实现、
// router源码hash.js
export class HTML5History extends History {
...
go (n: number) {window.history.go(n)}
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {const { current: fromRoute } = thisthis.transitionTo(location, route => { // History类上的funcpushHash(route.fullPath)handleScroll(this.router, route, fromRoute, false)onComplete && onComplete(route)}, onAbort)}function pushHash (path) {if (supportsPushState) { // 是否浏览器环境且环境支持pushstat方法,这个func下面会说pushState(getUrl(path)) // 支持的话往window.history添加一条数据} else {window.location.hash = path // 不支持的话直接修改location的hash}
}replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {const { current: fromRoute } = thisthis.transitionTo(location, route => {replaceHash(route.fullPath)handleScroll(this.router, route, fromRoute, false)onComplete && onComplete(route)}, onAbort)}
// 其实replace和push只有两个区别
1.
window.location.hash = path
window.location.replace(getUrl(path))
2.
if (replace) { // replace调这个func会传一个truehistory.replaceState({ key: _key }, '', url)
} else {_key = genKey()history.pushState({ key: _key }, '', url)
}
...
}
还有一点就是,在初始化hash模式路由的时候,会执行一个func,监听hashchange事件
setupListeners () {window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', () => {const current = this.currentif (!ensureSlash()) {return}this.transitionTo(getHash(), route => {if (supportsScroll) {handleScroll(this.router, route, current, true)}if (!supportsPushState) {replaceHash(route.fullPath)}})})
}
第二种:HTML5History模式
HTML5--History 科普
主要是新增的两个api
1.History.pushState()
[优点写的清清楚楚]
HTML5History的push、replace跟hash模式的差不多,就不上代码了
一个标记是否支持HTML5的flag,这么写的,有需要的可以刨回去用
export const supportsPushState = inBrowser && (function () {const ua = window.navigator.userAgentif ((ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&ua.indexOf('Mobile Safari') !== -1 &&ua.indexOf('Chrome') === -1 &&ua.indexOf('Windows Phone') === -1) {return false}return window.history && 'pushState' in window.history
})()
还有一个就是scrollBehavior,用来记录路由跳转的时候滚动条的位置,这个只能在HTML5模式下使用,即支持pushState方法的时候,部分博客说只有在HTML5History下才能使用,这个等我明天验证一下,我个人觉得支持HTML5就可以了
2.History.replaceState()
说的也很直观,就是不创新新纪录而覆盖一条记录,just do it
结束语
别问第三种情况(我是谁、我在哪、谁打我)
我兜子好沃,早知道不做前端了~
在学习router源码的时候阅读了熵与单子的代码本的文章,看完这篇文章配合源码基本都可以很好掌握vue-router的大概,感谢作者,另外说明下本文由本人学习结束后加上自己的理解一字一字敲出来的,可能有些相似之处,侵删请联系我,写文章的目的是看看自己能否表述清楚,对知识点的掌握情况,讲的不对的地方,请各位大佬指正~
~感谢潘童鞋的指导(^▽^)
当然,我也稀罕你的小❤❤,点个赞再走咯~
以上图片均来自MDN网页截图、vue官网截图、百度首页截图,不存在版权问题 /滑稽
【注】:内容有不当或者错误处请指正~转载请注明出处~谢谢合作!
简述vue-router实现原理相关推荐
- 面试题 请简述vue双向数据绑定原理
MVVM模式 MVVM模式就是Model–View–ViewModel模式.它实现了View的变动,自动反映在 ViewModel,反之亦然.就是在单向绑定的基础上给可输入元素(input.texta ...
- 简述vue响应式原理
vue官方阐述:https://cn.vuejs.org/v2/guide/reactivity.html 响应式数据的最终目标,是当对象本身或对象属性发生变化时,将会运行一些函数,最常见的就是ren ...
- Vue Router 原理分析与实现
陈丹青:看过的东西走过的路,还有你所经历的一切,都会经历一个开眼界的过程. 但眼界开了并不是一件好事情,反而顿悟之后从此你就会知道,你在社会之中是完全没有位置可言的.我算个屁,什么事都有人做过,都有人 ...
- Vue响应式原理简述
Vue响应式原理简述 依赖技术 图解过程 依赖技术 问题:Vue如何监听data的改变? => 技术:Object.defineProperty函数.在其中的set方法中监听对象属性的改变. 问 ...
- Vue 学习(十、 Vue Router - 路由插件 和 Vue 的插件原理)
文章目录 一.Vue Router - 路由插件 1. 安装 Vue Router 2. Vue Router 路由的基本使用 3. 设置 router-link 4. 编程式路由 5. 路由嵌套 6 ...
- 你可能不清楚的 Vue Router 深度用法(一)
Vue Router 简单易上手,能实现大部分的需求.但是,如果在项目里需要更细致的控制路由,以实现与其同步的效果,就需要挖掘其文档里没详细提及的内容.第一章为路由元信息用途挖掘. 路由元信息用途 ( ...
- vue router name命名规范_vue-router使用
vue-router基本用法 vue-router的实现原理: 路由不同的页面也就是加载不同的组件. 路由的三个基本概念: router:它是一条路由,test按钮 => test内容,这是一条 ...
- 已解决vue-router4路由报“[Vue Router warn]: No match found for location with path“
vue-router4动态加载的模式下,当我们在当前页面刷新浏览器时,会出现一个警告 [Vue Router warn]: No match found for location with path ...
- Vue学习笔记(六)--- 前端路由 Vue Router
一.路由 1.概念 路由本质上来说就是一种对应关系,比如说我们在浏览器中输入不同的 url 地址,我们就能访问不同的资源,那么这 url 地址与资源之间的对应关系,就是路由. 2.分类 ① 后端路 ...
- 【面试题】面试官:vue的这些原理你了解吗?
前言 在之前面试的时候我自己也经常会遇到一些vue原理的问题, 我也总结了下自己的经常的用到的,方便自己学习,今天也给大家分享出来, 欢迎大家一起学习交流, 有更好的方法欢迎评论区指出, 后序我也将持 ...
最新文章
- 【AAAI2022】TLogic:时序知识图谱上可解释链接预测的时间逻辑规则
- 删除无序单链表中值重复出现的节点
- 百度网盘的这个收作业的功能不好用
- 无水印pdf编辑器_偷偷告诉你如何编辑PDF文件,轻松解决这万恶的千古难题
- 行业职业病+生活安全事项
- java泛型方法 通配符_Java泛型教程–示例类,接口,方法,通配符等
- 如果你正在使用CocoaPods,你的.gitignore会有什么?
- apache worker性能调优
- C# 置顶EXE 把EXE放最前面 ProcessStartInfo
- 浅析iOS程序设计模式(基于MVC)
- 3分钟教你绘制一个围棋棋盘
- 小白入门必看 ‘微信小程序地图定位开发教程‘
- .axf文件_MDK 的编译过程及文件类型全解(一)
- 转:天下互联CEO张向宁:傻目录不是搜索引擎
- 1秒等于多少微妙,纳秒
- 起底硅谷最神秘、估值最高的大数据公司:Palantir
- 读到良葛格的反思Hello World
- jitsi-meet react 框架改造
- 生信分析之R语言常用R包一步下载
- 【Android App】在线语音识别功能实现(使用云知声平台与WebSocket 超详细 附源码)
热门文章
- 【C语言】printf()函数详解
- Java字符串连接的几种方式
- 程序员常见的健康问题
- 我国第二套人民币纸分币明天起停止流通
- 周华健,歌声伴我成长(五)
- 小小c#算法题 - 1 - 找出数组中满足条件的两个数
- 2018-2019-20175203 实验二 《Java面向对象程序设计》
- 《转》武​汉​的​I​T​公​司
- Cannot drop the database ‘XXX’ because it is being used for replication.
- 【辨异】entice, tempt, lure, seduce, induce