Vue 源码之手写Vue Router
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
方法实现
install
当Vue.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.pushState
API ,作用是改变地址不会向服务端发起请求.
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相关推荐
- Vue源码分析-手写Vue(简易版)
1.Vue双向绑定/MVVM响应式原理/v-model的原理 vue.js通过数据劫持结合发布订阅者模式,通过Object.defineProperty来劫持各个属性的setter,getter,在数 ...
- Vue源码解读一:Vue数据响应式原理
这方面的文章很多,但是我感觉很多写的比较抽象,本文会通过举例更详细的解释.(此文面向的Vue新手们,如果你是个大牛,看到这篇文章就可以点个赞,关掉页面了.)通过阅读这篇文章,你将了解到: 1.Vue数 ...
- 面试必会之ArrayList源码分析手写ArrayList
作者:Java知音-微笑面对生活 简介 ArrayList是我们开发中非常常用的数据存储容器之一,其底层是数组实现的,我们可以在集合中存储任意类型的数据,ArrayList是线程不安全的,非常适合用于 ...
- 源码分析 | 手写mybait-spring核心功能(干货好文一次学会工厂bean、类代理、bean注册的使用)
小傅哥 | https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获.专注于原创专题案例编写,目前已完成的专题有:Netty4.x实战专题案例.用Java实现JVM.基于Ja ...
- 推荐一本Vue源码阅读书籍《Vue.js技术内.幕》
1. 概述 这幅图大家应该都很清楚: 但这个过程在Vue框架中是如何实现的呢? 是否考虑过如果是自己,该如何设计呢?而这本<Vue.js技术内幕>就是一本非常好的关于Vue框架源码学习的书 ...
- 百度、阿里、滴滴、新浪的面试心经总结,源码+原理+手写框架
前言 作为一个程序员,如果你在新知识.新技术面前仍一无所知,依然吃着十多年前的老本,那你在知识技术上肯定落伍,如果又未能进入管理层面,那你肯定就会被长江的后浪拍在沙滩上了. 而不少与时俱进.善于学习的 ...
- 字节跳动面试:一线互联网大厂面试真题系统收录!源码+原理+手写框架
一.认识鸿蒙 鸿蒙 微内核是基于微内核的全场景分布式OS,可按需扩展,实现更广泛的系统安全,主要用于物联网,特点是低时延,甚至可到毫秒级乃至亚毫秒级. 鸿蒙OS实现模块化耦合,对应不同设备可弹性部署, ...
- Android性能优化最佳实践,源码+原理+手写框架
前言 众所周知,Android是一个基于Linux实现的操作系统.但对于Linux内核来说,Android也仅仅只是一个运行在内核之上的应用程序,与其他运行在内核之上的应用程序没有任何区别. 所以An ...
- 这份1307页Android面试全套真题解析,源码+原理+手写框架
前言 前不久,几个朋友聚会,谈到了现在的后辈,我就说起了那个大三就已经拿到网易offer的小学弟. 这个学弟是00后,专升本进入我们学校的.进来后就非常努力,每次上课都是第一个到教室的,每次都是坐第一 ...
最新文章
- 设置linux初始root密码
- Google Test(GTest)使用方法和源码解析——预处理技术分析和应用
- spring 使用redis集群配置
- 高并发应用场景下的负载均衡与故障转移实践,AgileEAS.NET SOA 负载均衡介绍与实践...
- asp.net导出到Excel的最佳选择
- 群集lvs—DR的配置及应用
- 7-7 六度空间 (30分)_近30年仅6人生涯总决赛首秀得分30+ 浓眉哥能成下一个吗
- hikaril连接sql2000_hikari连接池解析(版本:HikariCP-2.5.1.jar)
- 输入框不可以输入中文
- 【MySQL原理解析】01. 一条SQL查询语句是如何执行的
- OpenCV系统学习(基本了解完)
- 形式语义学关于带权有向图最短路径的有模式函数算法
- linux mk文件6,linux_2.6.30.4_Makefile_3--Makefile学习笔记
- Linux二进制保护(文末福利)
- Proteus总线连接心得
- 数学之美:谈谈密码学的数学原理
- 如何满足python安装需求升级win7到SP1版本
- 变分原理(Variational Principle)
- Python学习记录——Python容器:列表、元组、字典与集合(1)
- Vue.js使用Echarts动态渲染多个图表