理解

随着前端业务的发展,

我们一般在写一个较为大型的vue项目时候,会使用到vue-router,来根据指定的url或者hash来进行内容的分发,可以达到不像服务端发送请求,就完成页面内容的切换,能够减少像服务器发送的请求,让用户进行页面跳转时候能够更快,体验更好

疑问

在初学vue-router的时候,一般人都会有一个印象,router-link以及router-view都是vue原生自带的标签。但是这个印象是错误的,vue-router本质上是一个vue的插件,通过Vue.use(VueRouter)来使用这个插件。router-link以及router-view也是这个插件实现的自定义标签。

本文以开发插件的模式,撸一个vue-router插件以加深对其原理的了解

url变化流程图解

也就是说,要实现一个简单的vue-router,需要完成以下需求

具体操作

创建vue项目

vue create my-vue-router

由于只着重于vue-router的内容,所以先使用原本的vue-router这样只替换vue-router源码文件即可

增加vue-router

vue add router

然后项目目录就变成了

    my-vue-router
|- node_modules
|- public
|- src
|- assets
|- components
|- HellowWorld.vue
|- router
|- index.js
|- views
|- About.vue
|- Home.vue
|- App.vue
|- main.js
|- .gitinore
|- babel.config.js
|- package.json
|- README.md
|- yarn.lock

在目录中,新建一个myRouter.js的文件,来放置我们的源码

新建自己的myRouter文件

my-vue-router
|- node_modules
|- public
|- src
|- assets
|- components
|- HellowWorld.vue
|- router
|- index.js
+ |- myRouter.js
|- views
|- About.vue
|- Home.vue
|- App.vue
|- main.js
|- .gitinore
|- babel.config.js
|- package.json
|- README.md
|- yarn.lock

切换引入文件为自己写的myRouter.js

此时,@/src/router/index.js中的内容里,我们将导入的vue-router替换为我们的myRouter.js

  import Vue from 'vue'
- import VueRouter from 'vue-router'
+ import VueRouter from './myRouter'
import Home from '../views/Home.vue' Vue.use(VueRouter) const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
] const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
}) export default router

这里我们可以看到,代码执行的流程为

引入myRouter.js->配置routes对象->new VueRouter->export default 导出

此处用到了 Vue.use()这个API

Vue.use()

vue中的插件,一个核心的api就是vue.use()

安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为install 方法。install 方法调用时,会将 Vue 作为参数传入。

该方法需要在调用 new Vue() 之前被调用。

当 install 方法被同一个插件多次调用,插件将只会被安装一次。

也就是说,我们在自己造的myRouter里得实现这个install方法

需求

  1. 提供一个构造类,能够使用new VueRouter来生成实例
  2. 实现install方法
  3. 监听url变化,并双向绑定current方法
  4. 注册自定义组件router-linkrouter-view
  5. 实现用户配置的路由数组到map的转换,方便快速的查询到路由匹配的对象

实现

let Vue;//由于使用者肯定是使用vue.use引入的这个插件,所以代码里就不引入vue.js了,防止重复打包
// 需求1 声明一个拥有constructor构造器的class
class VueRouter{
constructor(options={}){// 构造函数
this.$options=options;// 保存配置项
this.app = { // 声明一个拥有current的变量,已完成路由的双向绑定
current:"/"
}
Vue.util.defineReactive(this.app,'current',this.app.current);//vue的拦截方法,会该值增加get拦截以收集依赖,set拦截以触发双向绑定
this.routerMap={}; // 创建key-value模式的routerMap,便于使用key能够快速的找到即将render(渲染)的组件
this.init(options); // 执行init方法,以完成需求3,4,5
}
init(options={}){
this.bindBrowserEvents()// 绑定浏览器事件
this.initComponent()//注册router-view及router-link组件
this.createRouterMap(options.routes)//创建key-value模式的routerMap
}
createRouterMap(arr=[]){ // 创建routerMap
arr.forEach(item => {
this.routerMap[item.path]=item
});
// 处理完后routerMap格式如下
// this.routerMap = {
// '/':{
// path: '/',
// name: 'Home',
// component: Home
// },
// '/about':{
// path: '/about',
// name: 'About',
// component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
// }
// }
}
bindBrowserEvents(){ // hash模式监听 hashchange 方法
window.addEventListener('load',this.onHashChange.bind(this))
window.addEventListener('hashchange',this.onHashChange.bind(this))
}
initComponent(){ // 注册自定义组件RouterLink及RouterView
Vue.component('RouterLink',{
props: {
to: String
},
render(h) {
return h('a',{
attrs:{
href:'#'+this.to
}
},this.$slots.default)
},
})
Vue.component('RouterView',{
render:(h)=>{
const component = this.routerMap[this.app.current].component
return h(component)
},
})
}
onHashChange(){ // hash变化时,改变 this.app.current
window.location.hash = window.location.hash || '/'; // 如果hash没有值,则默认给补一个/#/
if(this.routerMap[window.location.hash.slice(1)]){ // this.app.current = hash值
this.app.current = window.location.hash.slice(1);
}else{
this.app.current = '/';
} // 此处执行完后,则由于双向绑定,会触发routerView进行重新渲染
}
} // 需求2 实现install方法
VueRouter.install = function(_Vue){
Vue = _Vue; // 因为一定会先走install,所以将这个传入的Vue实例,保存到变量Vue中
}

注释都写在代码里啦,可以执行简单的路由双向绑定功能,有哪里有疑问可以提出互相学习

觉得好的话,可以给我的 github点个star

撸一个简单的vue-router来剖析原理的更多相关文章

  1. 搭建Vue.js环境,建立一个简单的Vue项目

    基于vue-cli快速构建 Vue是近年来比较火的一个前端框架,所以搭建Vue.js环境,要装webpack,vue-cli,Vue 安装webpack命令如下 $ cnpm install webp ...

  2. 手把手教你从零写一个简单的 VUE

    本系列是一个教程,下面贴下目录~1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 今天给大家带来的是实现一个简单的类似 VUE 一样的前端框架,VUE 框架现在应 ...

  3. 一个简单的 vue.js 实践教程

    https://segmentfault.com/a/1190000006776243?utm_source=tuicool&utm_medium=referral 感觉需要改善的地方有: ( ...

  4. 撸一个简单的MVVM例子

    我个人以为mvvm框架里面最重要的一点就是VM这部分,它要与Model层建立联系,将Model层转换成可以被View层识别的数据结构:其次也要同View建立联系,将数据及时更新到View层上,并且响应 ...

  5. 实现一个简单的Vue插件

    我们先看官方文档对插件的描述 插件通常会为 Vue 添加全局功能.插件的范围没有限制--一般有下面几种: 1.添加全局方法或者属性,如: vue-custom-element 2.添加全局资源:指令/ ...

  6. mvvm实现一个简单的vue

    vue,基于mvvm模式下的一个前端框架 mvvm模式下简单的实现数据代理,数据劫持 1.是用Object.defineProperty 实现数据代理 2.使用发布订阅者模式,配合 Object.de ...

  7. 徒手撸一个简单的RPC框架

    来源:https://juejin.im/post/5c4481a4f265da613438aec3 之前在牛逼哄哄的 RPC 框架,底层到底什么原理得知了RPC(远程过程调用)简单来说就是调用远程的 ...

  8. 60行代码实现一个迷你版Vue Router

    这是一个超级精简版的VueRouter,实现hash模式下,hash改变组件切换的功能,原理就是利用了 Vue.js 的响应式机制触发router-view组件的重新渲染. 代码 https://gi ...

  9. 【mpvue】使用Mpvue撸一个简单的小程序

    一.快速创建一个mpvue项目   全局安装 vue-cli   (如果有就不需要装了) 创建一个基于mpvue-quickstart模板的新项目,记得选择安装vuex vue init mpvue/ ...

随机推荐

  1. Dynamics 365 基于 Sql Server 2017 安装 报表 问题

    如果使用SQL2017 安装D365 会发现 SSRS与AD不能在同一台服务器上,因为无法安装SSRS,而无SSRS 则D365是无法继续安装的. 所以解决方法有二个: 1.另外准备一台服务器,不需要 ...

  2. 使用 pyautogui 进行跨平台的 GUI 自动化操作

    有个朋友最近问我有没有推荐 GUI 桌面应用自动化的技术,我只能回答他:不好意思,这个真有,他是 pyautogui.主要有三大特征: 纯纯的 python, 源码一览无余: 跨平台,linux, w ...

  3. wangeditor在移动端的web应用

    废话不多说,直接上代码 前端(前端多说一句,在初始使用阶段,不知道是怎么回事,复制在看云上的文档的配置参数时,一直有错误,后台获取不到$_file,整整一上午,下午上网搜了一下别人的上传图片代码才好用 ...

  4. 如何搭建一个WEB服务器项目(五)—— Controller返回JSON字符串

    从服务器获取所需数据(JSON格式) 观前提示:本系列文章有关服务器以及后端程序这些概念,我写的全是自己的理解,并不一定正确,希望不要误人子弟.欢迎各位大佬来评论区提出问题或者是指出错误,分享宝贵经验 ...

  5. Rasa init报错:AttributeError: type object 'Callable' has no attribute '_abc_registry'

    错误:Rasa init --no-prompt 报错 原因:Python升级到3.7后会遇到该问题 解决:pip uninstall typing

  6. docker 容器核心技术

    容器的数据卷(volume)也是占用磁盘空间,可以通过以下命令删除失效的volume: [root@localhost]# sudo docker volume rm $(docker volume ...

  7. Centos 7 下自启动服务配置

    在服务器部署服务后,往往需要将服务设置成开机自启的状态 ,以防设备出现宕机或断电重启,服务无法访问的情况. 对于常见的服务(httpd,mysqld,nginx)来说,可通过系统 systemctl ...

  8. mysql小白系列_03 体系结构-线程池

    thread pool的原理是什么? 为什么用double write就能解决page坏的问题? Innodb redo log 与 binlog有什么区别?有了Innodb redo log为什么还 ...

  9. logback-spring.xml配置

    logback-spring.xml配置 <?xml version="1.0" encoding="UTF-8"?> <configurat ...

  10. hdu3861他的子问题是poj2762二分匹配+Tarjan+有向图拆点 其实就是求DAG的最小覆盖点

    The King’s Problem Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Other ...