详解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 相关文章推荐
jQuery获取当前对象标签名称的方法
Feb 07 Javascript
jQuery的3种请求方式$.post,$.get,$.getJSON
Mar 28 Javascript
深入分析JQuery和JavaScript的异同
Oct 23 Javascript
jquery实现的缩略图预览滑块实例
Jun 25 Javascript
详解JavaScript的回调函数
Nov 20 Javascript
浅谈jQuery效果函数
Sep 16 Javascript
javascript 产生随机数的几种方法总结
Sep 26 Javascript
angular中不同的组件间传值与通信的方法
Nov 04 Javascript
vue改变对象或数组时的刷新机制的方法总结
Apr 24 Javascript
javascript定时器的简单应用示例【控制方块移动】
Jun 17 Javascript
Vue 实现一个命令式弹窗组件功能
Sep 25 Javascript
浅谈javascript事件环微任务和宏任务队列原理
Sep 12 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 APC的安装与使用详解
2013/06/13 PHP
PHP命名空间(namespace)的使用基础及示例
2014/08/18 PHP
PHP中使用php5-ffmpeg撷取视频图片实例
2015/01/07 PHP
JSON字符串传到后台PHP处理问题的解决方法
2016/06/05 PHP
php用wangeditor3实现图片上传功能
2019/08/22 PHP
jquery原创弹出层折叠效果点击折叠弹出一个层
2014/03/12 Javascript
jquery动态改变div宽度和高度
2015/02/09 Javascript
AngularJS 入门教程之HTML DOM实例详解
2016/07/28 Javascript
vue2.0+koa2+mongodb实现注册登录
2018/04/10 Javascript
React为 Vue 引入容器组件和展示组件的教程详解
2018/05/03 Javascript
详解如何在Vue项目中导出Excel
2019/04/19 Javascript
JS操作Fckeditor的一些常用方法(获取、插入等)
2020/02/19 Javascript
python 装饰器功能以及函数参数使用介绍
2012/01/27 Python
Python3.0与2.X版本的区别实例分析
2014/08/25 Python
Python自动生产表情包
2017/03/17 Python
代码讲解Python对Windows服务进行监控
2018/02/11 Python
python3实现字符串的全排列的方法(无重复字符)
2018/07/07 Python
Python SELENIUM上传文件或图片实现过程
2019/10/28 Python
解决Jupyter notebook中.py与.ipynb文件的import问题
2020/04/21 Python
基于python实现操作git过程代码解析
2020/07/27 Python
Python实现Canny及Hough算法代码实例解析
2020/08/06 Python
CSS3 filter(滤镜)实现网页灰色或者黑色模式的示例代码
2021/02/24 HTML / CSS
越南母婴用品购物网站:Kids Plaza
2020/04/09 全球购物
技校教师求职简历的自我评价
2013/10/20 职场文书
财务管理个人自荐书范文
2013/11/24 职场文书
读书心得体会
2013/12/28 职场文书
校园安全广播稿
2014/02/08 职场文书
小区消防演习方案
2014/02/21 职场文书
乡镇计划生育工作汇报
2014/10/28 职场文书
离婚案件原告代理词
2015/05/23 职场文书
幼儿园师德师风心得体会
2016/01/12 职场文书
MySQL Threads_running飙升与慢查询的相关问题解决
2021/05/08 MySQL
MySQL EXPLAIN输出列的详细解释
2021/05/12 MySQL
Python操作CSV格式文件的方法大全
2021/07/15 Python
Go语言 详解net的tcp服务
2022/04/14 Golang
Android学习之BottomSheetDialog组件的使用
2022/06/21 Java/Android