vue-router的原理?让我们来一个vue-router吧!
前言在使用vue全家桶进行开发的时候,其中一个重要的插件就是vue-router。Vue-router功能强大且易于使用。它是我们构建项目时不可或缺的一部分。那么什么是vue-router呢?它是如何实现的?那我们就从源码开始,开始一个vue-router的基本使用。首先我们来看看我们在项目中是如何使用router的router文件的。在项目中,我们通常会有一个router文件夹来存放我们的router文件//src/router/index.jsimportVuefrom'vue';从'vue-router'导入VueRouter;从“@/pages/detail1”导入Page1;从“@/pages/detail2”导入Page2;//创建VurRouter插件Vue.use(VueRouter);//配置路由器=newVueRouter(路由器);导出默认路由器;vue根实例文件实例化挂载vue根实例的时候,我们会在配置项中引入vue-router//src/main.jsimportVuefrom'vue';从'./App.vue'导入应用程序;从“./router”导入路由器;newVue({路由器,渲染:h=>;h(App),}).$mount('#app')利用路由提供的组件,我们可以根据需要显示和跳转路由page1|page2
手写vue-router闲话多,开始手写vue-router,走起!实现路由器的思路是先实现路由器。我们一定要清楚这次要做什么,先列一下:实现并导出一个Router实例,供外部调用。实现一个公共组件RouterView,用于加载路由视图。实现一个RouterLink,用于跳过路由,梳理各个组件的需求。Router实例需要接收router传入的options,处理收集所有路由和嵌套路由,监听url变化并且作为插件存在,应该有一个RouterView组件的install方法,根据自身的路由嵌套层级,找出对应路由中的组件,并显示RouterLink组件,创建一个a标签,插入写入的文字,并根据跳转到props中的值开始写代码分析需求。我们先写一个vue-router实例classVueRouter{constructor(options){//传入的路由配置保存在属性$options中,方便后面调用this.$options=options;}}创建了实例方法,我们开始写install方法//在install中缓存参数Vue,这样做的原因有两个://1.这里的Vue实例在上面的VueRouter实例中也会用到,并且缓存后可以直接使用//2.因为插件是独立的,直接引入Vue会导致打包的时候Vue被打包到插件中,增加了打包后的代码体积varVue;VueRouter.install=function(_Vue){Vue=_Vue;//使用mixin和生命周期beforeCreate做全局mixin,获取实例后的相关属性挂载到原型上Vue.mixin({beforeCreate(){if(this.$options.router){//这里我们可以看看为什么我们可以使用this.$router来获取路由器实例router;}}})}接下来对路由器实例进行完善//这里我们使用hash的方式构造路由地址,也就是我们经常看到的http://localhost/#/xxx//截取url,去掉#号this.current=window.location.hash.slice(1)||'/';//使用Vue中的defineReactive方法创建响应式匹配的数组,用于存放当前路由及其嵌套路由Vue.util.defineReactive(this,'matched',[]);//处理路由和嵌套路由this.matchs(this.$options);//监听获取地址和修改地址window.addEventListener('hashchange',this.currentHash.bind(this));//还需要在页面第一次加载时监听并修改地址window.addEventListener('load',this.currentHash.bind(this));}currentHash(){this.current=window.location.hash.slice(1);this.matched=[];这个.匹配(这个。$选项);}matches(routers){//递归处理路由//输出结果一般是['parent','child1','child1-child1']routers.forEach(router=>{//一般情况下嵌套路由不会存放在/路径下if(this.current==='/'&&router.path==='/'){this.matched.push(路由器);}elseif(this.current.includes(router.path)&&router.path!=='/'){this.matched.push(router);如果(router.children){this.matches(router.children);}}}}}至此,根据需求,路由器实例基本完成。让我们编写RouterView组件。在实现RouterView组件的过程中,我们需要思考一个问题,即如何找到对应的RouterView视图,并匹配到对应的路由组件。这里根据matched的存储方式引入一个概念,routingdepth,代表路由层次。//既然是全局组件,我们选择写在install里注册VueRouter.install=function(_Vue){Vue=_Vue;//挂载router-view组件Vue.component('RouterView',{render(h){//上面提到的深度标记,父组件的深度为0letdeep=0;//标记组件this.routerView=true;//循环查找RouterView的所有父元素并标记深度//在父子通信中使用$parentletparent=this.$parent;//循环查找父组件并标记深度当前组件,当父组件不存在时,表示已经到达顶层组件,退出循环while(parent){//父组件存在且为routerViewif(parent&&parent.routerView){deep+=1;}parent=parent.$parent;}//找到匹配的深度层级,找到componentletmatched=this.$router.matched;let_component=null;//如果能在matched中找到,赋值_component的值if(matched[deep]){_component=matched[deep].comp内容;}//渲染_component返回h(_component);}})}至此,RouterView也已经完成,我们只剩下一个组件了。最后,让我们实现RouterLink组件。从需求上看,这个组件与RouterView相比是非常简单的。只需要渲染一个a标签,显示相应的文字并跳转即可。和RouterView一样,我们写VueRouter.install=function(_Vue){Vue=_Vue;//挂载router-link组件Vue.component('RouterLink',{//声明props,并根据需要设置props:{to:{type:String,required:true}},render(h){'a',{attrs:{href:`#${this.to}`}},//使用匿名槽将组件中的文本写入到[this.$slots.default]}})}RouterLink组件的功能也完成了,是不是很简单的呢子呢?完整代码varVue;classVueRouter{constructor(options){//将传入的路由配置保存在属性$options中,方便后面调用this.$options=options;//这里我们使用hash的方式来构造路由地址,也就是我们经常看到的http://localhost/#/xxx//拦截url,去掉#号this.current=window.location.hash.slice(1)||'/';//使用Vue中的defineReactive方法创建响应式匹配的数组,存放当前路由及其embeddingSetroutingVue.util.defineReactive(this,'matched',[]);//处理路由和嵌套路由this.matchs(this.$options);//监听获取地址和修改地址window.addEventListener('hashchange',this.currentHash.bind(this));//页面第一次加载时,同样需要监听修改地址window.addEventListener('load',this.currentHash.bind(this));}currentHash(){这个.current=window.location.hash.slice(1);this.matched=[];this.matchs(this.$options);}matches(routers){//递归处理路由//输出结果一般是['parent','child1','child1-child1']routers.forEach(router=>{//一般情况下嵌套路由不会存储在/path下if(this.current==='/'&&router.path==='/'){this.matched.push(router);}elseif(this.current.includes(router.path)&&router.path!=='/'){this.matched.push(路由器);如果(router.children){this.matchs(router.children);}}}}}VueRouter.install=function(_Vue){Vue=_Vue;//使用mixin和生命周期beforeCreate做全局混合,获取实例后的相关属性挂载到原型上Vue.mixin({beforeCreate(){if(this.$options.router){//这里我们可以看到whywecanusethis.$Theroutergetstherouterinstance//你也可以看到为什么路由器实例要在main.js中的根实例中配置为optionsVue.prototype.$router=this.$options.router;}}})//挂载router-view组件Vue.component('RouterView',{render(h){//上面提到的深度标记,父组件的深度为0letdeep=0;//标记组件this.routerView=true;//循环查找RouterView的所有父元素并标记深度//在父子通信中使用$parentletparent=this.$parent;//循环查找父组件标记当前组件的深度,当父组件不存在时,说明已经到达顶层组件,退出循环while(paren吨){//父组件存在且为routerViewif(parent&&parent.routerView){deep+=1;}parent=parent.$parent;}//找到匹配的深度级别,找到组件letmatched=this.$router.matched;让_component=null;//如果能在matched中找到,则给_component赋值if(matched[deep]){_component=matched[deep].components;}//渲染_component返回h(_component);}})//挂载router-link组件Vue.component('RouterLink',{//声明props,并设置为必需props:{to:{type:String,required:true}},render(h){'a',{attrs:{href:`#${this.to}`}},//使用匿名槽在组件中写入文本[this.$slots.default]}})}导出默认VueRouter;写到最后,这一次vue-router的介绍就基本结束了。其实这里实现的只是最简单最基础的vue-router,也是我学习过程的一个总结,给大家做一个扩展和介绍。路由的重定向、懒加载、路由守卫等都没有体现在里面。大家可以按照这里分析的思路去阅读和学习源码。我天赋不高,文笔也一般。不敢说授人以渔,哪怕是给大家一点暗示和启发也是极好的。有不懂的可以一起讨论。如有错误,欢迎大家指正,共同学习,共同进步。下一篇可能会写一篇关于vuex的实现的文章。喜欢的同学点个赞也很棒,hhhhh。最后,源码在我的github上,觉得有帮助的同学可以给个小star。