当前位置: 首页 > Web前端 > vue.js

10分钟彻底搞懂单页应用路由

时间:2023-04-01 02:07:35 vue.js

上次和大家科普一下小程序的自定义路由,开启路由之旅;不擅长聊天……退……一块钱?单页应用特征假设:在一个网页中,有一个按钮,点击它可以跳转到站点内的其他页面。多页应用:点击按钮重新加载一个html资源,刷新整个页面;单页应用:点击按钮,没有新的html请求,只发生局部刷新,可以营造出接近原生的体验,丝滑般流畅。为什么SPA单页应用可以几乎没有刷新?因为它的SP-单页。首次进入应用时,返回唯一的html页面及其公共静态资源。后面所谓的“跳转”不再从服务器端拿html文件,而只是一个DOM替换操作。)为(壮)。那么js是如何捕捉组件切换的时机,在不刷新的情况下改变浏览器url的呢?依赖hash和HTML5History。hash路由功能类似于www.xiaoming.html#bar是hash路由。当#后面的哈希值发生变化时,就不会再向服务器请求数据了。可以通过hashchange事件监听URL的变化,从而进行DOM操作。模拟页面跳转不需要服务器配合。对SEO不友好。HTML5History路由特性History模式是HTML5的一个新特性,比hash路由方式更直观。貌似是这个www.xiaoming.html/bar,模拟页面跳转是通过history.pushState(state,title,url)更新浏览器路由。当路由发生变化时,监听popstate事件来操作DOM。需要后台配合。重定向对SEO比较友好。以vue-router为例,我们来看看它的源码。Tips:因为本文的重点是讲解单页路由的两种模式,所以下面只列出部分关键代码,主要讲解:注册插件VueRouter的构造函数,区分路由模式全局注册组件hash/pushofHTML5Historymode用监听方法transitionTo方法注册插件首先作为插件,要有意识地暴露一个install方法给Vue爸爸使用。在源码的install.js文件中,定义了注册安装插件的方法install,该方法混入到各个组件的hook函数中,执行beforeCreatehook时初始化路由:mixin({beforeCreate(){if(isDef(this.$options.router)){this._routerRoot=thisthis._router=this.$options.routerthis._router.init(this)Vue.util.defineReactive(this,'_route',this._router.history.current)}else{this._routerRoot=(this.$parent&&this.$parent._routerRoot)||this}registerInstance(this,this)},//在完整的text,...表示省略的方法...});区分模式然后,我们从index.js中找到整个插件的基类VueRouter。不难看出,它在构造函数中根据不同的模式使用了不同的路由实例。...从'./install'导入{install};从'./history/hash'导入{HashHistory};从'./history/html5'导入{HTML5History};...导出默认类VueRouter{静态安装:()=>无效;constructor(options:RouterOptions={}){if(this.fallback){mode='hash'}if(!inBrowser){mode='abstract'}this.mode=modeswitch(mode){case'历史':this.history=newHTML5History(this,options.base)打破case'hash':this.history=newHashHistory(this,options.base,this.fallback)breakcase'abstract':this.history=newAbstractHistory(this,options.base)breakdefault:if(process.env.NODE_ENV!=='production'){assert(false,`invalidmode:${mode}`)}}}}全局注册router-link组件此时,我们可能会问:在使用vue-router时,常见的在哪里引入?回到install.js文件,引入并全局注册router-view和router-link组件:import从'./components/view'查看;从'./components/link'导入链接;...Vue.component('RouterView',View);Vue.component('RouterLink',Link);在./components/link.js中,组件默认绑定了点击事件,点击触发handler方法执行相应的路由操作。consthandler=e=>{if(guardEvent(e)){if(this.replace){router.replace(location,noop)}else{router.push(location,noop)}}};开头提到,VueRouter的构造函数针对不同的模式初始化了不同模式的History实例,所以router.replace和router.push的方法也不同。接下来我们分别拉取这两种模式的源码。在hash模式history/hash.js文件中,定义了HashHistory类,它继承自history/base.js的History基类。它的原型定义了一个push方法:在支持HTML5History模式(supportsPushState为true)的浏览器环境下,调用history.pushState改变浏览器地址;在其他浏览器环境下,直接使用location.hash=path替换为新的哈希地址。其实我一开始看这个的时候是有一些疑惑的。既然已经是hash模式了,为什么还需要判断supportsPushState呢?history.pushState为了支持scrollBehavior,可以传过去的参数key,这样每一个urlhistory都有一个key,每条路由的位置信息都用key保存。同时原型绑定的setupListeners方法负责监听hash变化的时机:在支持HTML5History模式的浏览器环境中,监听popstate事件;在其他浏览器中,听hashchange。监听到变化后,触发handleRoutingEvent方法,调用父类的transitionTo跳转逻辑,进行DOM替换操作。import{pushState,replaceState,supportsPushState}from'../util/push-state'...exportclassHashHistoryextendsHistory{setupListeners(){...consthandleRoutingEvent=()=>{constcurrent=this.currentif(!ensureSlash()){return}//transitionTo调用的父类History下的跳转方法,跳转后路径会进行hash化this.transitionTo(getHash(),route=>{if(supportsScroll){handleScroll(this.router,route,current,true)}if(!supportsPushState){replaceHash(route.fullPath)}})}consteventType=supportsPushState?'popstate':'hashchange'window.addEventListener(eventType,handleRoutingEvent)this.listeners.push(()=>{window.removeEventListener(eventType,handleRoutingEvent)})}push(location:RawLocation,onComplete?:Function,onAbort?:函数){const{当前:fromRoute}=thisthis.transitionTo(location,route=>{pushHash(route.fullPath)handleScroll(this.router,route,fromRoute,false)onComplete&&onComplete(route)},onAbort)}}...//处理传输入路径成散列形式的URLfunctiongetUrl(path){consthref=window.location.hrefconsti=href.indexOf('#')constbase=i>=0?href.slice(0,i):hrefreturn`${base}#${path}`}...//替换hashfunctionpushHash(path){if(supportsPushState){pushState(getUrl(path))}else{window.location.hash=path}}//util/push-state.js文件中的方法exportconstsupportsPushState=inBrowser&&(function(){constua=window.navigator.userAgentif((ua.indexOf('Android2.')!==-1||ua.indexOf('Android4.0')!==-1)&&ua.indexOf('MobileSafari')!==-1&&ua.indexOf('Chrome')===-1&&ua.indexOf('WindowsPhone')===-1){returnfalse}rreturnwindow.history&&typeofwindow.history.pushState==='function'})()HTML5History模式类似,HTML5History类定义在history/html5.js中定义push原型方法,调用history.pushState修改浏览器的路径。同时原型setupListeners方法监听popstate事件,适时替换DOM。import{pushState,replaceState,supportsPushState}from'../util/push-state';...exportclassHTML5HistoryextendsHistory{setupListeners(){consthandleRoutingEvent=()=>{constcurrent=this.current;constlocation=getLocation(this.base);if(this.current===START&&location===this._startLocation){return}this.transitionTo(location,route=>{if(supportsScroll){handleScroll(router,route,current,true)}})}window.addEventListener('popstate',handleRoutingEvent)this.listeners.push(()=>{window.removeEventListener('popstate',handleRoutingEvent)})}push(location:RawLocation,onComplete?:Function,onAbort?:Function){const{current:fromRoute}=thisthis.transitionTo(location,route=>{pushState(cleanPath(this.base+route.fullPath))handleScroll(this.router,route,fromRoute,false)onComplete&&onComplete(route)},onAbort)}}...//util/push-state.js文件中的方法exportfunctionpushState(url?:string,replace?:boolean){saveScrollPosition()consthistory=window.historytry{if(replace){conststateCopy=extend({},history.state)stateCopy.key=getStateKey()history.replaceState(stateCopy,'',url)}else{history.pushState({key:setStateKey(genStateKey())},'',url)}}catch(e){window.location[替换?'replace':'assign'](url)}}transitionTo处理路由变化逻辑上面提到的两种路由模式,This.transitionTo在监听的时候触发。这是什么?它实际上是在history/base.js基类上定义的原型方法。用于处理路由的变化逻辑。比较并返回对应的路由对象;然后判断新路由和当前路由是否相同,相同则直接返回;如果不是,则执行this.confirmTransition中的回调更新路由对象,并替换视图相关的DOM。exportclassHistory{...transitionTo(location:RawLocation,onComplete?:Function,onAbort?:Function){constroute=this.router.match(location,this.current)this.confirmTransition(route,()=>{constprev=this.currentthis.updateRoute(route)onComplete&&onComplete(route)this.ensureURL()this.router.afterHooks.forEach(hook=>{hook&&hook(route,prev)})if(!this.ready){this.ready=truethis.readyCbs.forEach(cb=>{cb(route)})}},err=>{if(onAbort){onAbort(err)}if(err&&!this.ready){this.ready=true//https://github.com/vuejs/vue-router/issues/3225if(!isRouterError(err,NavigationFailureType.redirected)){this.readyErrorCbs.forEach(cb=>{cb(呃)})}else{this.readyCbs.forEach(cb=>{cb(route)})}}})}...}最后,以上是单页路由的一些小知识。希望我们一起开始永不放弃~~