详解Vue-Router源码分析路由实现原理


Posted in Javascript onMay 15, 2019

深入Vue-Router源码分析路由实现原理

使用Vue开发SPA应用,离不开vue-router,那么vue和vue-router是如何协作运行的呢,下面从使用的角度,大白话帮大家一步步梳理下vue-router的整个实现流程。

到发文时使用的版本是:
- vue (v2.5.0)
- vue-router (v3.0.1)

一、vue-router 源码结构

github 地址:https://github.com/vuejs/vue-router

详解Vue-Router源码分析路由实现原理

components下是两个组件<router-view> 和 <router-link>

history是路由方式的封装,提供三种方式

util下主要是各种功能类和功能函数

create-matcher和create-router-map是生成匹配表

index是VueRouter类,也整个插件的入口

Install 提供安装的方法

先整体展示下vue-router使用方式,请牢记一下几步哦。

import Vue from 'vue'
import VueRouter from 'vue-router'
//注册插件 如果是在浏览器环境运行的,可以不写该方法
Vue.use(VueRouter)

// 1. 定义(路由)组件。
// 可以从其他文件 import 进来
const User = { template: '<div>用户</div>' }
const Role = { template: '<div>角色</div>' }

// 2. 定义路由
// Array,每个路由应该映射一个组件。
const routes = [
 { path: '/user', component: User },
 { path: '/home', component: Home }
]

// 3. 创建 router 实例,并传 `routes` 配置
const router = new VueRouter({
 routes 
})

// 4. 创建和挂载根实例。
// 记得要通过 router 对象以参数注入Vue,
// 从而让整个应用都有路由功能
// 使用 router-link 组件来导航.
// 路由出口
// 路由匹配到的组件将渲染在这里
const app = new Vue({
 router,
 template: `
  <div id="app">
   <h1>Basic</h1>
   <ul>
    <li><router-link to="/">/</router-link></li>
    <li><router-link to="/user">用户</router-link></li>
    <li><router-link to="/role">角色</router-link></li>
    <router-link tag="li" to="/user">/用户</router-link>
   </ul>
   <router-view class="view"></router-view>
  </div>
 `
}).$mount('#app')

分析开始

第一步

Vue是使用.use( plugins )方法将插件注入到Vue中。
use方法会检测注入插件VueRouter内的install方法,如果有,则执行install方法。
注意:如果是在浏览器环境,在index.js内会自动调用.use方法。如果是基于node环境,需要手动调用。

if (inBrowser && window.Vue) {
 window.Vue.use(VueRouter)
}

Install解析 (对应目录结构的install.js)

该方法内主要做了以下三件事:

  1. 1、对Vue实例混入beforeCreate钩子操作(在Vue的生命周期阶段会被调用)
  2. 2、通过Vue.prototype定义router、router、route 属性(方便所有组件可以获取这两个属性)
  3. 3、Vue上注册router-link和router-view两个组件

 

export function install (Vue) {
 if (install.installed && _Vue === Vue) return
 install.installed = true

 _Vue = Vue

 const isDef = v => v !== undefined

 const registerInstance = (vm, callVal) => {
  let i = vm.$options._parentVnode
  if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
   i(vm, callVal)
  }
 }

 Vue.mixin({
  //对Vue实例混入beforeCreate钩子操作
  beforeCreate () {
   if (isDef(this.$options.router)) {
    this._routerRoot = this
    this._router = this.$options.router
    this._router.init(this)
    Vue.util.defineReactive(this, '_route', this._router.history.current)
   } else {
    this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
   }
   registerInstance(this, this)
  },
  destroyed () {
   registerInstance(this)
  }
 })
 //通过Vue.prototype定义$router、$route 属性(方便所有组件可以获取这两个属性)
 Object.defineProperty(Vue.prototype, '$router', {
  get () { return this._routerRoot._router }
 })

 Object.defineProperty(Vue.prototype, '$route', {
  get () { return this._routerRoot._route }
 })
 //Vue上注册router-link和router-view两个组件
 Vue.component('RouterView', View)
 Vue.component('RouterLink', Link)

 const strats = Vue.config.optionMergeStrategies
 // use the same hook merging strategy for route hooks
 strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}

第二步 生成router实例

const router = new VueRouter({
 routes 
})

生成实例过程中,主要做了以下两件事

  1. 1、根据配置数组(传入的routes)生成路由配置记录表。
  2. 2、根据不同模式生成监控路由变化的History对象

注:History类由HTML5History、HashHistory、AbstractHistory三类继承
history/base.js实现了基本history的操作
history/hash.js,history/html5.js和history/abstract.js继承了base,只是根据不同的模式封装了一些基本操作

第三步 生成vue实例

const app = new Vue({
 router,
 template: `
  <div id="app">
   <h1>Basic</h1>
   <ul>
    <li><router-link to="/">/</router-link></li>
    <li><router-link to="/user">用户</router-link></li>
    <li><router-link to="/role">角色</router-link></li>
    <router-link tag="li" to="/user">/用户</router-link>
   </ul>
   <router-view class="view"></router-view>
  </div>
 `
}).$mount('#app')

代码执行到这,会进入Vue的生命周期,还记得第一步Vue-Router对Vue混入了beforeCreate钩子吗,在此会执行哦

Vue.mixin({
  beforeCreate () {
   //验证vue是否有router对象了,如果有,就不再初始化了
   if (isDef(this.$options.router)) { //没有router对象
    //将_routerRoot指向根组件
    this._routerRoot = this
    //将router对象挂载到根组件元素_router上
    this._router = this.$options.router
    //初始化,建立路由监控
    this._router.init(this)
    //劫持数据_route,一旦_route数据发生变化后,通知router-view执行render方法
    Vue.util.defineReactive(this, '_route', this._router.history.current)
   } else {
    //如果有router对象,去寻找根组件,将_routerRoot执行根组件(解决嵌套关系时候_routerRoot指向不一致问题)
    this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
   }
   registerInstance(this, this)
  },
  destroyed () {
   registerInstance(this)
  }
 })

代码执行到这,初始化结束,界面将显示默认首页

路由更新方式:

一、主动触发

router-link绑定了click方法,触发history.push或者history.replace,从而触发history.transitionTo。
transitionTo用于处理路由转换,其中包含了updateRoute用于更新_route。
在beforeCreate中有劫持_route的方法,当_route变化后,触发router-view的变化。

二、地址变化(如:在浏览器地址栏直接输入地址)

HashHistory和HTML5History会分别监控hashchange和popstate来对路由变化作对用的处理 。
HashHistory和HTML5History捕获到变化后会对应执行push或replace方法,从而调用transitionTo
,剩下的就和上面主动触发一样啦。

总结

 1、安装插件

混入beforeCreate生命周期处理,初始化_routerRoot,_router,_route等数据
全局设置vue静态访问router和router和route,方便后期访问
完成了router-link和 router-view 两个组件的注册,router-link用于触发路由的变化,router-view作 为功能组件,用于触发对应路由视图的变化

2、根据路由配置生成router实例

根据配置数组生成路由配置记录表
生成监控路由变化的hsitory对象

3、将router实例传入根vue实例

根据beforeCreate混入,为根vue对象设置了劫持字段_route,用户触发router-view的变化
调用init()函数,完成首次路由的渲染,首次渲染的调用路径是 调用history.transitionTo方法,根据router的match函数,生成一个新的route对象
接着通过confirmTransition对比一下新生成的route和当前的route对象是否改变,改变的话触发updateRoute,更新hsitory.current属性,触发根组件的_route的变化,从而导致组件的调用render函数,更新router-view
另外一种更新路由的方式是主动触发

router-link绑定了click方法,触发history.push或者history.replace,从而触发history.transitionTo
同时会监控hashchange和popstate来对路由变化作对用的处理

以上所述是小编给大家介绍的Vue-Router源码分析路由实现原理详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
实现连缀调用的map方法(prototype)
Aug 05 Javascript
jquery网页元素拖拽插件效果及实现
Aug 05 Javascript
JavaScript引用类型和基本类型详解
Jan 06 Javascript
jQuery弹出层后禁用底部滚动条(移动端关闭回到原位置)
Aug 29 Javascript
JS基于面向对象实现的选项卡效果示例
Dec 20 Javascript
ES6 let和const定义变量与常量的应用实例分析
Jun 27 Javascript
Vue学习之组件用法实例详解
Jan 06 Javascript
记一次react前端项目打包优化的方法
Mar 30 Javascript
怎么理解wx.navigateTo的events参数使用详情
May 18 Javascript
浅谈vue-props的default写不写有什么区别
Aug 09 Javascript
vue.js封装switch开关组件的操作
Oct 26 Javascript
Vue组件更新数据v-model不生效的解决
Apr 02 Vue.js
微信小程序select下拉框实现效果
May 15 #Javascript
详解js常用分割取字符串的方法
May 15 #Javascript
elementUI table表格动态合并的示例代码
May 15 #Javascript
详解VUE调用本地json的使用方法
May 15 #Javascript
微信小程序的mpvue框架快速上手指南
May 15 #Javascript
解决微信小程序云开发中获取数据库的内容为空的方法
May 15 #Javascript
JS中使用react-tooltip插件实现鼠标悬浮显示框
May 15 #Javascript
You might like
Snoopy类使用小例子
2008/04/15 PHP
Optimizer与Debugger兼容性问题的解决方法
2008/12/01 PHP
PHP封装的MSSql操作类完整实例
2016/05/26 PHP
php利用gd库为图片添加水印
2016/11/09 PHP
PHP利用二叉堆实现TopK-算法的方法详解
2017/04/24 PHP
PHP读取CSV大文件导入数据库的实例
2017/07/24 PHP
PHP反射原理与用法深入分析
2019/09/28 PHP
使用prototype.js 的时候应该特别注意的几个问题.
2007/04/12 Javascript
javascript-TreeView父子联动效果保持节点状态一致
2007/08/12 Javascript
用cookies实现的可记忆的样式切换效果代码下载
2007/12/24 Javascript
Jquery跨域获得Json时invalid label错误的解决办法
2011/01/11 Javascript
遨游,飞飞,IE,空中网 浏览器无提示关闭方法
2011/07/11 Javascript
JQuery select控件的相关操作实现代码
2012/09/14 Javascript
js下拉菜单语言选项简单实现
2013/09/23 Javascript
jQuery超赞的评分插件(8款)
2015/08/20 Javascript
微信小程序 Flex布局详解
2016/10/09 Javascript
JS中判断null的方法分析
2016/11/21 Javascript
Nodejs 和 Electron ubuntu下快速安装过程
2018/05/04 NodeJs
其实你可以少写点if else与switch(推荐)
2019/01/10 Javascript
JavaScript碰撞检测原理及其实现代码
2020/03/12 Javascript
Python工程师面试题 与Python Web相关
2016/01/14 Python
浅析python中SQLAlchemy排序的一个坑
2017/02/24 Python
python实现逆序输出一个数字的示例讲解
2018/06/25 Python
Python变量类型知识点总结
2019/02/18 Python
解决Pycharm 包已经下载,但是运行代码提示找不到模块的问题
2019/08/31 Python
css3 border旋转时的动画应用
2016/01/22 HTML / CSS
世界上最大的巴士旅游观光公司:Big Bus Tours
2016/10/20 全球购物
老教师工作总结的自我评价
2013/09/27 职场文书
管理学专业个人求职信范文
2013/12/13 职场文书
工厂实习感言
2014/01/14 职场文书
志愿者活动总结报告
2014/06/27 职场文书
卫生院艾滋病宣传活动小结
2014/07/09 职场文书
2016年社区服务活动总结
2016/04/06 职场文书
2019邀请函格式及范文
2019/05/20 职场文书
Python实现生成bmp图像的方法
2021/06/13 Python
一文搞清楚MySQL count(*)、count(1)、count(col)区别
2022/03/03 MySQL