详解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 相关文章推荐
通过DOM脚本去设置样式信息
Sep 19 Javascript
javascript修改表格背景色实例代码分享
Dec 10 Javascript
js 数值转换为3位逗号分隔的示例代码
Feb 19 Javascript
new Date()问题在ie8下面的处理方法
Jul 31 Javascript
浅谈JavaScript中的分支结构
Jul 01 Javascript
EasyUI 结合JS导出Excel文件的实现方法
Nov 10 Javascript
JavaScript中数组Array方法详解
Feb 27 Javascript
JS判断时间段的实现代码
Jun 14 Javascript
jQuery插件artDialog.js使用与关闭方法示例
Oct 09 jQuery
通过fastclick源码分析彻底解决tap“点透”
Dec 24 Javascript
浅谈vue异步数据影响页面渲染
Oct 29 Javascript
微信小程序利用for循环解决内容变更问题
Mar 05 Javascript
微信小程序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
论建造顺序的重要性
2020/03/04 星际争霸
PHP树的深度编历生成迷宫及A*自动寻路算法实例分析
2015/03/10 PHP
js查找父节点的简单方法
2008/06/28 Javascript
JQuery之拖拽插件实现代码
2011/04/14 Javascript
Angular中的Promise对象($q介绍)
2015/03/03 Javascript
基于jQuey实现鼠标滑过变色(整行变色)
2015/12/07 Javascript
浏览器兼容性问题大汇总
2015/12/17 Javascript
jQuery Validate插件实现表单强大的验证功能
2015/12/18 Javascript
JavaScript中三种异步上传文件方式
2016/03/06 Javascript
Node.js复制文件的方法示例
2016/12/29 Javascript
vue2.x 父组件监听子组件事件并传回信息的方法
2017/07/17 Javascript
JavaScript原型对象、构造函数和实例对象功能与用法详解
2018/08/04 Javascript
vue+VeeValidate 校验范围实例详解(部分校验,全部校验)
2018/10/19 Javascript
uni-app微信小程序登录并使用vuex存储登录状态的思路详解
2019/11/04 Javascript
Vue数字输入框组件示例代码详解
2020/01/15 Javascript
JavaScript运行机制实例分析
2020/04/11 Javascript
Vue微信公众号网页分享的示例代码
2020/05/28 Javascript
javascript实现滚轮轮播图片
2020/12/13 Javascript
Python中的引用和拷贝浅析
2014/11/22 Python
Python中json格式数据的编码与解码方法详解
2016/07/01 Python
使用python遍历指定城市的一周气温
2017/03/31 Python
Python进阶_关于命名空间与作用域(详解)
2017/05/29 Python
Python字符串处理实现单词反转
2017/06/14 Python
python实现k-means聚类算法
2018/02/23 Python
python实现最大子序和(分治+动态规划)
2019/07/05 Python
Django项目使用CircleCI的方法示例
2019/07/14 Python
python 命令行传入参数实现解析
2019/08/30 Python
如何使用python3获取当前路径及os.path.dirname的使用
2019/12/13 Python
美国特价机票专家:Airfarewatchdog
2018/01/24 全球购物
Monki官网:斯堪的纳维亚的独立时尚品牌
2020/11/09 全球购物
什么是事务?为什么需要事务?
2012/01/09 面试题
大学新生入学教育方案
2014/05/16 职场文书
个人授权委托书样本
2014/09/13 职场文书
Python批量将csv文件转化成xml文件的实例
2021/05/10 Python
pytorch Dropout过拟合的操作
2021/05/27 Python
详解JavaScript的计时器和按钮效果设置
2022/02/18 Javascript