Vue 源码之手写Vue Router

源码地址:https://github.com/CONOR007/Handwritten-routing

一.Vue Router的两种模式

hash模式实现原理

  • URl中#后的内容作为路径地址
  • 监听hashchange事件
  • 根据当前路由地址找到对应的组件重新渲染

History模式实现原理

  • 通过history.pushState()方法改变地址栏
  • 监听popstate事件
  • 根据当前路由地址找到对应组件重新渲染

二.Vue Router的使用

vue作为一个渐进式框架取决于它有强大的插件机制,通过注册对应的插件得到想要的功能.

Vue.use可以接受一个对象类,调用类中的静态方法install实现功能. 也可以接受一个函数并直接调用.

import Vue from 'vue'
import VueRouter from 'vue-router'
// 1. 注册路由插件
Vue.use(VueRouter)// 2. 创建 router 对象
const router = new VueRouter({routes
})new Vue({// 3. 注册 router 对象router,render: h => h(App)
}).$mount('#app')

类图

根据类图中提供的属性和方法去实现对应的功能,能让我们的目标更加清晰.

如上可见,这个名为VueRouter的类它有三个属性和六个方法.其中+号是对外公开的代码,_号是静态方法.

三个属性:

options属性是记录构造函数传入的对象,比如记录我们在new VueRouter的时候传入的routes对象规则;

routeMap是一个对象用来记录路由地址和组件的对应关系,将来我们会将options对应到routeMap中来.

data是通过vue.obsverber存储响应式数据的,因为路由地址更改过户对应的组件要更新. 它里边有一个属性current是用来记录当前路由地址的

六个方法:

install是一个静态方法,用来实现vue的插件机制;

Constructor构造函数是用来初始化对应的属性;

init用来初始化功能方法;

``initEvent`是用来注册popState事件监听浏览器历史的变化;

createRouteMap是用来初始化routeMap属性的把传入的路由规则转换成键值对的形式存储到routeMap中去,键是路由的地址,值是路由的组件;

initComponents是用来创建和这两个组件的.

三.具体实现

这里主要介绍实现历史模式,hash模式同理

install方法实现

installVue.use(VueRouter)时会被调用,它是一个静态方法,用来实现vue的插件机制,它接收一个Vue对象.通过Vue对象的mixin能在第一次实例化的时候拿到router.并实现路由方法的初始化.

let _Vue = null
export default class VueRouter {static install (Vue) {// 1.判断当前插件是否已经被安装if (VueRouter.install.installed) returnVueRouter.install.installed = true// 2.把Vue构造函数记录到全局变量_Vue = Vue// 3.把创建Vue实例时传入的router注入到_Vue上. 混入_Vue.mixin({beforeCreate () {if (this.$options.router) {// 创建的时候vue肯定是可以拿到router的 这个时候把它给_Vue的原型_Vue.prototype.$router = this.$options.routerthis.$options.router.init()}}})}init () {//初始化方法}
}

注意this.$options拿到的是Vue初始化时传入的对象.所以接下来我们在该类中实现的所有属性和方法都能在this.$options.router中拿到.

const vm = new Vue({router,render: h => h(App)}).$mount('#app')

constructor的实现

如下是截取路由规则与实例化代码部分代码

// 路由规则
const routes = [{path: '/',name: 'Index',component: Index},{path: '/blog',name: 'Blog',component: () => import(/* webpackChunkName: "blog" */ '../views/Blog.vue')},{path: '/photo',name: 'Photo',component: () => import(/* webpackChunkName: "photo" */ '../views/Photo.vue')}
]
const router = new VueRouter({mode: 'history',routes
})

constructor接收实例化路由对象时传进来的对象,它的作用是初始化属性.

options参数就是new VueRouter时传入的对象 { mode: 'history', routes},其中routeMap是一个对象用来记录路由地址和组件的对应关系.

constructor (options) {this.options = optionsthis.routeMap = {}// observable实现current的双向绑定this.data = _Vue.observable({current: '/'})
}

createRouteMap的实现

createRouteMap遍历路由规则,解析成键值对存储在routeMap中.键是路由的地址,值是路由的组件.

  createRouteMap () {this.options.routes.forEach(element => {this.routeMap[element.path] = element.component})}

initComponents的实现

initComponents主要创建路由中所用到的<route-link><route-view>这两个组件.使用.component创建组件,render渲染组件,h函数创建目标元素或生成虚拟DOM.clickHandler用来实现路由的跳转,在历史模式中主要用到HTML5的history.pushStateAPI ,作用是改变地址不会向服务端发起请求.

initComponents (Vue) {Vue.component('router-link', {props: {to: String},// template: '<a :href="to"><slot></slot></a>'render (h) {// h函数(生成的目标元素,目标元素属性,内容部分插槽)return h('a',{attrs : {href : this.to},on : {click:this.clickHandler},},[this.$slots.default])},methods: {clickHandler (e) {history.pushState({},'',this.to)this.$router.data.current = this.to//组织a标签的默认事件e.preventDefault();}},})const self = thisVue.component('router-view', {render(h) {// component 当前路由地址const component = self.routeMap[self.data.current]// h可以帮我们创建虚拟DOMreturn h(component)},})
}

这里要注意:完整版本的Vue支持template编译 运行时不支持template如果要使用需要在vue.config.js中配置 runtimeCompiler 或者配置render函数

initEvent实现

initEvent用来注册popState事件监听浏览器历史的变化,也就是点击浏览器左上角回退时要更新组件

initEvent () {window.addEventListener('popstate',()=>{this.data.current = window.location.pathname})
}

init初始化方法

init () {this.createRouteMap()this.initComponents(_Vue)this.initEvent()
}

四.完整代码

let _Vue = null
export default class VueRouter {// install是一个静态方法,用来实现vue的插件机制static install (Vue) {// 1.判断当前插件是否已经被安装if (VueRouter.install.installed) {return}VueRouter.install.installed = true// 2.把Vue构造函数记录到全局变量_Vue = Vue// 3.把创建Vue实例时传入的router注入到_Vue上. 混入_Vue.mixin({beforeCreate () {// 只需要在实例化的时候执行if (this.$options.router) {// const vm = new Vue({//     注册 router 对象//     router,//     render: h => h(App)// }).$mount('#app')// 实例化的时候 new Vue中的对象都放在$options中去了. 所以该类中所有属性方法都能在this.$options.router中拿到._Vue.prototype.$router = this.$options.routerthis.$options.router.init()}}})}// 构造函数初始化 属性值constructor (options) {// options是实例化路由对象时传进来的对象// {//     mode: 'history',//     routes// }// 是记录构造函数传入的对象,比如记录我们在new VueRouter的时候传入的routes对象规则;this.options = options// 是一个对象用来记录路由地址和组件的对应关系,将来我们会将options对应到routeMap中来this.routeMap = {}// 通过vue.obsverber存储响应式数据的,因为路由地址更改过户对应的组件要更新. 它里边有一个属性current是用来记录当前路由地址的this.data = _Vue.observable({current: '/'})}init () {this.createRouteMap()this.initComponents(_Vue)this.initEvent()}// 遍历路由规则,解析成键值对存储在routeMap中createRouteMap () {this.options.routes.forEach(element => {// 键是路由的地址,值是路由的组件this.routeMap[element.path] = element.component})}// 创建<route-link>和<route-view>这两个组件initComponents (Vue) {Vue.component('router-link', {props: {to: String},// 注意:完整版本的Vue支持template编译 运行时不支持template如果要使用需要在vue.config.js中配置 runtimeCompiler 或者配置render函数// template: '<a :href="to"><slot></slot></a>'render (h) {// h函数(生成的目标元素,目标元素属性,内容部分插槽)return h('a', {attrs: {href: this.to},on: {click: this.clickHandler}}, [this.$slots.default])},methods: {clickHandler (e) {this.$router.mode(() => {history.pushState({}, '', this.to)this.$router.data.current = this.to}, () => {window.location.hash = this.to})// 组织a标签的默认事件e.preventDefault()}}})const self = thisVue.component('router-view', {render (h) {// component 当前路由地址const component = self.routeMap[self.data.current]// h可以帮我们创建虚拟DOMreturn h(component)}})}// initEvent 用来注册popState事件监听浏览器历史的变化initEvent () {const self = thiswindow.addEventListener('popstate', () => {this.mode(() => {self.data.current = window.location.pathname}, () => {})})window.addEventListener('hashchange', () => {this.mode(() => {self.data.current = window.location.pathname}, () => {if (self.data.current !== window.location.hash) self.data.current = window.location.hash.substr(1)})})}// 模式判断mode (his, mode) {if (this.options.mode === 'history') {his()} else if (this.options.mode === 'mode') {mode()}}
}

最后在路由中用上我们自己写的路由路径

...
import VueRouter from '../vueRouter/index.js' //这里
...

Vue 源码之手写Vue Router相关推荐

  1. Vue源码分析-手写Vue(简易版)

    1.Vue双向绑定/MVVM响应式原理/v-model的原理 vue.js通过数据劫持结合发布订阅者模式,通过Object.defineProperty来劫持各个属性的setter,getter,在数 ...

  2. Vue源码解读一:Vue数据响应式原理

    这方面的文章很多,但是我感觉很多写的比较抽象,本文会通过举例更详细的解释.(此文面向的Vue新手们,如果你是个大牛,看到这篇文章就可以点个赞,关掉页面了.)通过阅读这篇文章,你将了解到: 1.Vue数 ...

  3. 面试必会之ArrayList源码分析手写ArrayList

    作者:Java知音-微笑面对生活 简介 ArrayList是我们开发中非常常用的数据存储容器之一,其底层是数组实现的,我们可以在集合中存储任意类型的数据,ArrayList是线程不安全的,非常适合用于 ...

  4. 源码分析 | 手写mybait-spring核心功能(干货好文一次学会工厂bean、类代理、bean注册的使用)

    小傅哥 | https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获.专注于原创专题案例编写,目前已完成的专题有:Netty4.x实战专题案例.用Java实现JVM.基于Ja ...

  5. 推荐一本Vue源码阅读书籍《Vue.js技术内.幕》

    1. 概述 这幅图大家应该都很清楚: 但这个过程在Vue框架中是如何实现的呢? 是否考虑过如果是自己,该如何设计呢?而这本<Vue.js技术内幕>就是一本非常好的关于Vue框架源码学习的书 ...

  6. 百度、阿里、滴滴、新浪的面试心经总结,源码+原理+手写框架

    前言 作为一个程序员,如果你在新知识.新技术面前仍一无所知,依然吃着十多年前的老本,那你在知识技术上肯定落伍,如果又未能进入管理层面,那你肯定就会被长江的后浪拍在沙滩上了. 而不少与时俱进.善于学习的 ...

  7. 字节跳动面试:一线互联网大厂面试真题系统收录!源码+原理+手写框架

    一.认识鸿蒙 鸿蒙 微内核是基于微内核的全场景分布式OS,可按需扩展,实现更广泛的系统安全,主要用于物联网,特点是低时延,甚至可到毫秒级乃至亚毫秒级. 鸿蒙OS实现模块化耦合,对应不同设备可弹性部署, ...

  8. Android性能优化最佳实践,源码+原理+手写框架

    前言 众所周知,Android是一个基于Linux实现的操作系统.但对于Linux内核来说,Android也仅仅只是一个运行在内核之上的应用程序,与其他运行在内核之上的应用程序没有任何区别. 所以An ...

  9. 这份1307页Android面试全套真题解析,源码+原理+手写框架

    前言 前不久,几个朋友聚会,谈到了现在的后辈,我就说起了那个大三就已经拿到网易offer的小学弟. 这个学弟是00后,专升本进入我们学校的.进来后就非常努力,每次上课都是第一个到教室的,每次都是坐第一 ...

最新文章

  1. 设置linux初始root密码
  2. Google Test(GTest)使用方法和源码解析——预处理技术分析和应用
  3. spring 使用redis集群配置
  4. 高并发应用场景下的负载均衡与故障转移实践,AgileEAS.NET SOA 负载均衡介绍与实践...
  5. asp.net导出到Excel的最佳选择
  6. 群集lvs—DR的配置及应用
  7. 7-7 六度空间 (30分)_近30年仅6人生涯总决赛首秀得分30+ 浓眉哥能成下一个吗
  8. hikaril连接sql2000_hikari连接池解析(版本:HikariCP-2.5.1.jar)
  9. 输入框不可以输入中文
  10. 【MySQL原理解析】01. 一条SQL查询语句是如何执行的
  11. OpenCV系统学习(基本了解完)
  12. 形式语义学关于带权有向图最短路径的有模式函数算法
  13. linux mk文件6,linux_2.6.30.4_Makefile_3--Makefile学习笔记
  14. Linux二进制保护(文末福利)
  15. Proteus总线连接心得
  16. 数学之美:谈谈密码学的数学原理
  17. 如何满足python安装需求升级win7到SP1版本
  18. 变分原理(Variational Principle)
  19. Python学习记录——Python容器:列表、元组、字典与集合(1)
  20. Vue.js使用Echarts动态渲染多个图表

热门文章

  1. 上拉电阻、下拉电阻功能
  2. 【数据结构】17-二叉排序树(BST)
  3. ubuntu18 分辨率只有800*600/640x480,怎么转1920x1080
  4. Android 11适配指南之系统相机拍照、打开相册,安卓app开发教程
  5. 将APP保卫战进行到底--为你的APP添加四道防线
  6. Aizu - 0531 Paint Color
  7. iOS4.3导致iPhone续航能力下降
  8. 简单了解JQuery中的attr及removeAttr
  9. 语音识别芯片模块LD3320到底甩了新唐ISD9160几条街
  10. 图像扭曲(Image Warping)