详解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 相关文章推荐
Prototype ObjectRange对象学习
Jul 19 Javascript
JavaScript 放大镜 放大倍率和视窗尺寸
May 09 Javascript
jQuery下的动画处理总结
Oct 10 Javascript
node.js中的fs.readSync方法使用说明
Dec 17 Javascript
简述AngularJS相关的一些编程思想
Jun 23 Javascript
JS+CSS实现的蓝色table选项卡效果
Oct 08 Javascript
在JavaScript中call()与apply()区别
Jan 22 Javascript
基于jQuery实现仿百度首页选项卡切换效果
May 29 Javascript
Vue组件化开发思考
Feb 02 Javascript
JS加密插件CryptoJS实现的DES加密示例
Aug 16 Javascript
自定义javascript验证框架示例【附源码下载】
May 31 Javascript
Vue使用虚拟dom进行渲染view的方法
Dec 26 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
php实现的简单压缩英文字符串的代码
2008/04/24 PHP
攻克CakePHP系列二 表单数据显示
2008/10/22 PHP
php 魔术方法使用说明
2009/10/20 PHP
php empty函数判断mysql表单是否为空
2010/04/12 PHP
PHP持久连接mysql_pconnect()函数使用介绍
2012/02/05 PHP
PHP中两个float(浮点数)比较实例分析
2015/09/27 PHP
PHP基于PDO调用sqlserver存储过程通用方法【基于Yii框架】
2017/10/07 PHP
TP(thinkPHP)框架多层控制器和多级控制器的使用示例
2018/06/13 PHP
PHP如何搭建百度Ueditor富文本编辑器
2018/09/21 PHP
jquery easyui 结合jsp简单展现table数据示例
2014/04/18 Javascript
javascript实现下班倒计时效果的方法(可桌面通知)
2015/07/10 Javascript
浏览器兼容性问题大汇总
2015/12/17 Javascript
详解nodejs与javascript中的aes加密
2016/05/22 NodeJs
浅析如何利用JavaScript进行语音识别
2016/10/27 Javascript
利用jquery实现验证输入的是否是数字、小数,包含保留几位小数
2016/12/07 Javascript
js通过Date对象实现倒计时动画效果
2017/10/27 Javascript
Vue集成Iframe页面的方法示例
2017/12/12 Javascript
微信小游戏之使用three.js 绘制一个旋转的三角形
2019/06/10 Javascript
在Angular中实现一个级联效果的下拉框的示例代码
2020/05/20 Javascript
vue 插槽简介及使用示例
2020/11/19 Vue.js
[05:03]2018DOTA2亚洲邀请赛主赛事首日回顾
2018/04/04 DOTA
python连接sql server乱码的解决方法
2013/01/28 Python
python读取oracle函数返回值
2016/07/18 Python
在IPython中进行Python程序执行时间的测量方法
2018/11/01 Python
Pytorch 计算误判率,计算准确率,计算召回率的例子
2020/01/18 Python
使用python 的matplotlib 画轨道实例
2020/01/19 Python
Python pandas对excel的操作实现示例
2020/07/21 Python
美国时装品牌:Nautica(诺帝卡)
2016/08/28 全球购物
茵宝(Umbro)英国官方商店:英国足球服装生产商
2016/12/29 全球购物
泰国演唱会订票网站:StubHub泰国
2018/02/26 全球购物
史上最全面的Java面试题汇总!
2015/02/03 面试题
市场部规章制度
2014/01/24 职场文书
校运会口号
2014/06/18 职场文书
师德师风自查总结
2014/10/14 职场文书
党员违纪检讨书怎么写
2014/11/01 职场文书
2016年9月份红领巾广播稿
2015/12/21 职场文书