路由是根据不一样的 url 地址展现不一样的内容或页面,早期的路由都是后端直接根据 url 来 reload 页面实现的,即后端控制路由。javascript
后来页面愈来愈复杂,服务器压力愈来愈大,随着AJAX(异步刷新技术) 的出现,页面实现非 reload 就能刷新数据,让前端也能够控制 url 自行管理,前端路由所以而生。html
若是直接使用AJAX加载页面片断是能够实现单页效果的,但这样会破坏浏览器的前进与后退功能,使用Hjax或Pjax技术后便可以实现单页无刷新效果又不会影响前进与后退功能。前端
示例:vue
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>模拟单页</title> </head> <body> <script src="../../js/jquery-1.12.4/jquery-1.12.4.js"></script> <a href="" id="page1">页面一</a> | <a href="" id="page2">页面二</a> <div id="container"> </div> <script> $("#page1").click(function () { $("#container").load("page1.html"); return false; }); $("#page2").click(function () { $.ajax({ url:"page2.html", type:"get", dataType:"html", success:function (data) { var ctx=$("<html/>").html(data); //方法一 console.log(ctx.find("div")); //方法二 console.log($("div",ctx)); $("#container").html($("body",ctx)); } }); return false; }); </script> </body> </html>
page1.html:html5
<div style="background: antiquewhite"> <h2>这是页面一</h2> </div>
page2.html:java
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>这是页面二</title> </head> <body> <div style="background: dodgerblue"> <h2>这是页面二</h2> </div> </body> </html>
运行结果:node
单页面应用的实现,就是由于前端路由,前端路由实现主要有下面几种方法:jquery
原理:url 中常会出现 #,一能够表示锚点(如回到顶部按钮的原理),二是路由里的锚点(hash)。Web 服务并不会解析 hash,也就是说 # 后的内容 Web 服务都会自动忽略,可是 JavaScript 是能够经过 window.location.hash 读取到的,读取到路径加以解析以后就能够响应不一样路径的逻辑处理。webpack
hashchange 事件(监听 hash 变化触发的事件),当用 window.location 处理哈希的改变时不会从新渲染页面,而是看成新页面加到历史记录中,这样咱们跳转页面就能够在 hashchange 事件中注册 ajax 从而改变页面内容。git
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>模拟单页 - hash</title> </head> <body> <script src="../../js/jquery-1.12.4/jquery-1.12.4.js"></script> <a href="#/page1.html" id="page1">页面一</a> | <a href="#/page2.html" id="page2">页面二</a> <button onclick="getHash()">得到hash</button> <button onclick="setHash()">修改hash</button> <div id="container"> </div> <script> $("a[href]").click(function () { var url = $(this).prop("href").split("#")[1].substring(1); $.ajax({ url: url, type: "get", dataType: "html", success: function (data) { var ctx = $("<html/>").html(data); var root = $("body", ctx); if (root.size() > 0) { $("#container").html(root); } else { $("#container").html(data); } } }); }); function getHash() { console(location.hash); } function setHash() { location.hash = "123456"; } </script> </body> </html>
运行结果:
这里并无解决前进与后退失效的问题。
URL是统一资源定位符,对能够从互联网上获得的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每一个文件都有一个惟一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。URI是统一资源标识符,而URL是统一资源定位符,咱们把URL理解为是URI的一个子类,而另外一种子类是URN。url是统一资源定位符,对能够从互联网上获得的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每一个文件都有一个惟一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。URL中全部的字符都是ASCII字符集,若是出现非ASCII字符集,好比中文,浏览器会先进行编码再进行传输。
URL的构成基本以下
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
举例以下:
http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
拆解以下:
http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
这里用户名是zhangguo,密码是123456,若是带@符用户必须填写,密码选填,带用户名与密码的状况不多见,不少时候都放到了参数中了。
http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
http://为协议名,标明了请求须要使用的协议,一般使用的是HTTP协议或者安全协议 HTTPS.其余协议还有mailto:用户打开邮箱的客户端,和ftp:用来作文件的转换, file用来获取文件,data获取外部资源等
http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
www.example.com为域名,标明了须要请求的服务器的地址,www是主机名,example是单位名称,.com是机构类型
http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
:80是端口号,标明了获取服务器资源的入口,端口号只有整数,范围是从0 到65535(2^16-1),周知端口是众所周知的端口号,范围从0到1023,其中80端口分配给WWW服务,21端口分配给FTP服务等。咱们在IE的地址栏里输入一个网址的时候是没必要指定端口号的,由于在默认状况下WWW服务的端口是“80”。动态端口的范围是从49152到65535。之因此称为动态端口,是由于它 通常不固定分配某种服务,而是动态分配。端口1024到49151,分配给用户进程或应用程序。这些进程主要是用户选择安装的一些应用程序,而不是已经分配好了公认端口的经常使用程序。这些端口在没有被服务器资源占用的时候,能够用用户端动态选用为源端口。
端口号用于区分服务的端口,一台拥有IP地址的服务器能够提供许多服务,好比Web服务、FTP服务、SMTP服务等.那么,服务器的资源经过“IP地址+端口号”来区分不一样的服务.
若是把服务器比做房子,端口号能够看作是通向不一样服务的门,
http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
/path/to/myfile.html表示服务器上资源的路径,过去这样的路径标记的是服务器上文件的物理路径,可是如今,路径表示的只是一个抽象地址,并不指代任何物理地址.
http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
?key1=value1&key2=value2是请求里提供的额外参数.这些参数是以键值对的形式,经过&符号分隔开来,服务器能够经过这些参数进行相应的个性化处理
http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
#SomewhereInTheDocument是对资源的部分补充.fragment能够理解为资源内部的书签.用来想服务器指明展现的内容所在的书签的点.例如对于HTML文件来讲,浏览器会滚动到特定的或者上次浏览过的位置.对于音频或者视频资源来讲,浏览器又会跳转到对应的时间节点。
锚记链接又叫命名锚记,命名锚记像一个迅速定位器同样是一种页面内的超级连接。
咱们上面所说的都是绝对路径,可是URL也有相对路径的表现形式.
URL所请求的资源依赖于请求所在的上下文,也就是当前环境,在浏览器的输入框内URL没有上下文,因此必须提供绝对路径.
可是当URL用于文件中时,例如HTML的页面,状况就大有不一样了,由于浏览器已经拥有了文件的URL,因此能够自动填补文件内使用的URL丢失的部分,例如协议,域名,端口等,因此咱们能够较为直观的区分相对路径和绝对路径.
若是URL以/开头,浏览器会从根服务器去获取资源,而不是从给定的文件夹中获取.
咱们用一些例子来直观的理解下
完整的URL:
https://developer.mozilla.org/en-US/docs/Learn
隐藏协议
//developer.mozilla.org/en-US/docs/Learn
浏览器会使用文件主机的相同协议
隐藏域名
/en-US/docs/Learn
浏览器会使用文件主机的相同协议和一样的域名,注意,不能在未隐藏协议的前提下只隐藏域名
<a href="page1.html" onclick="alert(this.href)">相对路径</a> <a href="/page1.html" onclick="alert(this.href)">绝对路径,从主机名开始</a>
结果:
hash即URL中"#"字符后面的部分,有不少种别名如ref、fragment、hash、Anchor,中文通常称为锚连接。
①使用浏览器访问网页时,若是网页URL中带有hash,页面就会定位到id(或name)与hash值同样的元素的位置;
②hash还有另外一个特色,它的改变不会致使页面从新加载;
③hash值浏览器是不会随请求发送到服务器端的;
④经过window.location.hash属性获取和设置hash值。
window.location.hash值的变化会直接反应到浏览器地址栏(#后面的部分会发生变化),同时,浏览器地址栏hash值的变化也会触发window.location.hash值的变化,从而触发onhashchange事件。
hash 属性是一个可读可写的字符串,该字符串是 URL 的锚部分(从 # 号开始的部分)
#表明网页中的一个位置。其右面的字符,就是该位置的标识符。好比,
http://www.example.com/index.html#print
就表明网页index.html的print位置。浏览器读取这个URL后,会自动将print位置滚动至可视区域。(单页应用)
为网页位置指定标识符,有两个方法。一是使用锚点,好比<a name="print"></a>,二是使用id属性,好比<div id="print" >。
示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>hash</title> </head> <body> <p style="height: 500px; background: blue" id="p1">第一段</p> <p style="height: 300px; background: red" id="p2">第二段</p> <p style="height: 500px; background: palegreen" id="p3">第三段</p> <div style="height: 3000px; background: dodgerblue"> </div> <a href="#p1" onclick="alert(this.href)">到第一段</a> | <a href="#p2">到第二段</a> | <a href="#p3">到第三段</a> <a href="page1.html" onclick="alert(this.href)">相对路径</a> <a href="/page1.html" onclick="alert(this.href)">绝对路径,从主机名开始</a> <form> <button>提交,是否带了hash</button> </form> </body> </html>
结果:
#是用来指导浏览器动做的,对服务器端彻底无用。因此,HTTP请求中不包括#。
好比,访问下面的网址,
http://www.example.com/index.html#print
浏览器实际发出的请求是这样的:
GET /index.html HTTP/1.1
Host: www.example.com
能够看到,只是请求index.html,根本没有"#print"的部分。
在第一个#后面出现的任何字符,都会被浏览器解读为位置标识符。这意味着,这些字符都不会被发送到服务器端。
好比,下面URL的原意是指定一个颜色值:
http://www.example.com/?color=#fffccc
可是,浏览器实际发出的请求是:
GET /?color= HTTP/1.1
Host: www.example.com
能够看到,"#fffccc"被省略了。只有将#转码为%23,浏览器才会将其做为实义字符处理。也就是说,上面的网址应该被写成:
http://example.com/?color=%23fffccc
单单改变#后的部分,浏览器只会滚动到相应位置,不会从新加载网页。
好比,从
http://www.example.com/index.html#location1
改为
http://www.example.com/index.html#location2
浏览器不会从新向服务器请求index.html。
每一次改变#后的部分,都会在浏览器的访问历史中增长一个记录,使用"后退"按钮,就能够回到上一个位置。
这对于ajax应用程序特别有用,能够用不一样的#值,表示不一样的访问状态,而后向用户给出能够访问某个状态的连接。
值得注意的是,上述规则对IE 6和IE 7不成立,它们不会由于#的改变而增长历史记录。
window.location.hash这个属性可读可写。读取时,能够用来判断网页状态是否改变;写入时,则会在不重载网页的前提下,创造一条访问历史记录。
默认状况下,Google的网络蜘蛛忽视URL的#部分。
可是,Google还规定,若是你但愿Ajax生成的内容被浏览引擎读取,那么URL中可使用"#!",Google会自动将其后面的内容转成查询字符串_escaped_fragment_的值。
好比,Google发现新版twitter的URL以下:
http://twitter.com/#!/username
就会自动抓取另外一个URL:
http://twitter.com/?_escaped_fragment_=/username
经过这种机制,Google就能够索引动态的Ajax内容。
这是一个HTML 5新增的事件,当#值发生变化时,就会触发这个事件。IE8+、Firefox 3.6+、Chrome 5+、Safari 4.0+支持该事件。
它的使用方法有三种:
window.onhashchange = func; <body onhashchange="func();"> window.addEventListener("hashchange", func, false);
对于不支持onhashchange的浏览器,能够用setInterval监控location.hash的变化。
示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>hashchange</title> </head> <body> <a href="#p1">到第一段</a> | <a href="#p2">到第二段</a> | <a href="#p3">到第三段</a> <script> addEventListener("hashchange", function (e) { console.log(e); }, false); </script> </body> </html>
结果:
newURL是当前URL,oldURL是原来的URL。
示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>history 测试</title> </head> <body> <p><input type="text" value="0" id="oTxt" /></p> <p><input type="button" value="+" id="oBtn" /></p> <script> var otxt = document.getElementById("oTxt"); var oBtn = document.getElementById("oBtn"); var n = 0; oBtn.addEventListener("click",function(){ n++; add(); },false); get(); function add(){ if("onhashchange" in window){ //若是浏览器的原生支持该事件 window.location.hash = "#"+n; } } function get(){ if("onhashchange" in window){ //若是浏览器的原生支持该事件 window.addEventListener("hashchange",function(e){ var hashVal = window.location.hash.substring(1); if(hashVal){ n = hashVal; otxt.value = n; } },false); } } </script> </body> </html>
结合前面的内容,咱们能够模拟一个简单的单页示例,为了提升性能这里作了缓存,示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>模拟单页 - hash</title> </head> <body> <script src="../../js/jquery-1.12.4/jquery-1.12.4.js"></script> <a href="#page1.html" id="page1">页面一</a> | <a href="#page2.html" id="page2">页面二</a> <div id="container"> </div> <script> //缓存 var pages={}; //显示内容 function showContent(data) { var ctx = $("<html/>").html(data); var root = $("body", ctx); if (root.size() > 0) { $("#container").html(root); } else { $("#container").html(data); } } //监听hash的变化 window.addEventListener("hashchange",function (e) { console.log(location.href); var url = location.hash.substring(1); console.log(url); if(pages[url]){ showContent(pages[url]); return false; } if(url) { $.ajax({ url: url, type: "get", dataType: "html", success: function (data) { showContent(data); //缓存到对象中 if(!pages.url){ pages[url]=data; } } }); } },false); </script> </body> </html>
结果:
虽然传统的ajax方式能够异步无刷新改变页面内容,但没法改变页面URL,所以有种方案是在内容发生改变后经过改变URL的hash的方式得到更好的可访问性(如 https://liyu365.github.io/BG-UI/tpl/#page/desktop.html),可是 hash 的方式有时候不能很好的处理浏览器的前进、后退,并且常规代码要切换到这种方式还要作很多额外的处理。而 pjax 的出现就是为了解决这些问题,简单的说就是对 ajax 的增强。
pjax结合pushState和ajax技术, 不须要从新加载整个页面就能从服务器加载Html到你当前页面,这个ajax请求会有永久连接、title并支持浏览器的回退/前进按钮。
pjax项目地址在 https://github.com/defunkt/jquery-pjax 。 实际的效果见: http://pjax.herokuapp.com 没有勾选 pjax 的时候点击连接是跳转的, 勾选了以后连接都是变成了 ajax 刷新(实际效果以下图的请求内容对比)。
在没有history ap以前,咱们常用散列值来改变页面内容,特别是那些对页面特别重要的内容。由于没有刷新,因此对于单页面应用,改变其URL是不可能的。此外,当你改变URL的散列值,它对浏览器的历史记录没有任何影响。经过增长location.hash,并用onhashchange来达到目的。
如今对于HTML 5的History API来讲,这些都是能够轻易实现的,可是因为单页面应用不必使用散列值,它可能须要额外的开发脚本。它也容许咱们用一种对SEO友好的方式创建新应用。
history.length属性保存着历史记录的URL数量。初始时,该值为1。因为IE10+浏览器在初始时返回2,存在兼容性问题,因此该值并不经常使用。
history.go(n),进前或后退n步
history.back(),后退
history.forward(),前进
若是移动的位置超出了访问历史的边界,以上三个方法并不报错,而是静默失败
使用历史记录时,页面一般从浏览器缓存之中加载,而不是从新要求服务器发送新的网页,不触发onload事件。
HTML5为history对象添加了两个新方法,history.pushState()和history.replaceState(),用来在浏览历史中添加和修改记录。state属性用来保存记录对象,而popstate事件用来监听history对象的变化(ie9不支持)。
history.pushState()方法向浏览器历史添加了一个状态。pushState()方法带有三个参数:一个状态对象、一个标题(如今被忽略了)以及一个可选的URL地址
history.pushState(state, title, url);
state object —— 状态对象是一个由pushState()方法建立的、与历史纪录相关的javascript对象。当用户定向到一个新的状态时,会触发popstate事件。事件的state属性包含了历史纪录的state对象。若是不须要这个对象,此处能够填null
title —— 新页面的标题,可是全部浏览器目前都忽略这个值,所以这里能够填null
URL —— 这个参数提供了新历史纪录的地址。新URL必须和当前URL在同一个域,不然,pushState()将丢出异常。这个参数可选,若是它没有被特别标注,会被设置为文档的当前URL
假定当前网址是example.com/1.html,使用pushState方法在浏览记录(history对象)中添加一个新记录
var stateObj = { foo: 'bar' }; history.pushState(stateObj, 'page 2', '2.html');
添加上面这个新记录后,浏览器地址栏马上显示example.com/2.html,但并不会跳转到2.html,甚至也不会检查2.html是否存在,它只是成为浏览历史中的最新记录。假如这时访问了google.com,而后点击了倒退按钮,页面的url将显示2.html,可是内容仍是原来的1.html。再点击一次倒退按钮,url将显示1.html,内容不变
总之,pushState方法不会触发页面刷新,只是致使history对象发生变化,地址栏的显示地址发生变化
若是pushState的url参数,设置了一个新的锚点值(即hash),并不会触发hashchange事件,,即便新的URL和旧的只在hash上有区别
若是设置了一个跨域网址,则会报错。这样设计的目的是,防止恶意代码让用户觉得他们是在另外一个网站上
history.replaceState方法的参数与pushState方法如出一辙,不一样之处在于replaceState()方法会修改当前历史记录条目而并不是建立新的条目
假定当前网页是example.com/example.html
history.pushState({page: 1}, 'title 1', '?page=1'); history.pushState({page: 2}, 'title 2', '?page=2'); history.replaceState({page: 3}, 'title 3', '?page=3'); history.back() // url显示为http://example.com/example.html?page=1 history.back() // url显示为http://example.com/example.html history.go(2) // url显示为http://example.com/example.html?page=3
示例:
history.state属性返回当前页面的state对象
history.pushState({page: 1}, 'title 1', '?page=1'); history.state// { page: 1 }
每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件
[注意]须要注意的是,仅仅调用pushState方法或replaceState方法,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用javascript调用back()、forward()、go()方法时才会触发。另外,该事件只针对同一个文档,若是浏览历史的切换,致使加载不一样的文档,该事件也不会触发
使用的时候,能够为popstate事件指定回调函数。这个回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前URL所提供的状态对象(即这两个方法的第一个参数)
上面代码中的event.state,就是经过pushState和replaceState方法,为当前URL绑定的state对象
这个state对象也能够直接经过history对象读取
var currentState = history.state;
示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>模拟单页 - hash</title> </head> <body> <script src="../../js/jquery-1.12.4/jquery-1.12.4.js"></script> <a href="#page1.html" id="page1">页面一</a> | <a href="#page2.html" id="page2">页面二</a> <div id="container"> </div> <script> //添加浏览历史变化事件 addEventListener("popstate",function (e) { console.log("popstate事件被引起,%o",e); },false); </script> </body> </html>
结果:
默认状况下,浏览器会在当前会话(session)缓存页面,当用户点击“前进”或“后退”按钮时,浏览器就会从缓存中加载页面
浏览器有一个特性叫“往返缓存”(back-forward cache或bfcache),能够在用户使用浏览器的“后退”和“前进”按钮时加快页面的转换速度。这个缓存中不只保存着页面数据,还保存了DOM和javascript的状态;其实是将整个页面都保存在了内存里。若是页面位于bfcache中,那么再次打开该页面时就不会触发load事件,IE10-浏览器不支持
pageshow事件在页面加载时触发,包括第一次加载和从缓存加载两种状况。若是要指定页面每次加载(无论是否是从浏览器缓存)时都运行的代码,能够放在这个事件的监听函数
第一次加载时,它的触发顺序排在load事件后面。从缓存加载时,load事件不会触发,由于网页在缓存中的样子一般是load事件的监听函数运行后的样子,因此没必要重复执行。同理,若是是从缓存中加载页面,网页内初始化的JavaScript脚本(好比DOMContentLoaded事件的监听函数)也不会执行
[注意]虽然这个事件的目标是document,但必须将其事件处理程序添加到window
pageshow事件有一个persisted属性,返回一个布尔值。页面第一次加载时或没有从缓存加载时,这个属性是false;当页面从缓存加载时,这个属性是true
[注意]上面的例子使用了私有做用域,以防止变量showCount进入全局做用域。若是单击了浏览器的“刷新”按钮,那么showCount的值就会被重置为0,由于页面已经彻底从新加载了
与pageshow事件对应的是pagehide事件,该事件会在浏览器卸载页面的时候触发,并且是在unload事件以前触发。与pageshow事件同样,pagehide在document上面触发,但其事件处理程序必需要添加到window对象
[注意]指定了onunload事件处理程序的页面会被自动排除在bfcache以外,即便事件处理程序是空的。缘由在于,onunload最经常使用于撤销在onload中所执行的操做,而跳过onload后再次显示页面极可能就会致使页面不正常
pagehide事件的event对象也包含persisted属性,不过其用途稍有不一样。若是页面是从bfcache中加载的,那么persisted的值就是true;若是页面在卸载以后会被保存在bfcache中,那么persisted的值也会被设置为true。所以,当第一次触发pageshow时,persisted的值必定是false,而在第一次触发pagehide时,persisted就会变成true(除非页面不会被保存在bfcache中)
window.onpagehide = function(e){ e = e || event; console.log(e.persisted); }
使用方法:
一、取消默认的返回操做
function pushHistory(){ var state = { title: "title", url: "#" } window.history.pushState(state, "title", "#"); } pushHistory()
二、history.js用于兼容html4,也能够监听pushState与replaceSatea
history在某些浏览器上支持还不是特别好,能够引用这个实现兼容的js
https://github.com/browserstate/history.js
利用ajax请求替代了a标签的默认跳转,而后利用html5中的API修改了url
API: history.pushState 和 history.replaceState
两个 API 都会操做浏览器的历史记录,而不会引发页面的刷新,pushState会增长一条新的历史记录,而replaceState则会替换当前的历史记录。
window.history.pushState(null, null, "name/foo"); //url: https://best.cnblogs.com/name/foo window.history.pushState(null, null, "/name/bar"); //url: https://best.cnblogs.com/name/bar
url是统一资源定位符,对能够从互联网上获得的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每一个文件都有一个惟一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
pjax是jquery的一个插件,它使用ajax和pushState两个技术改善用户的网页浏览体验。具体来讲,当用户使用a标签切换页面时,能够实现局部刷新的技术。
github源码:https://github.com/defunkt/jquery-pjax
pjax主要作两方面的事儿:
ajax
请求,服务器获得请求返回须要填充的HTML片断,客户端获得HTML片断而后插入更新区域pushState
更新当前的URL这个过程能实现页面局部刷新,比传统的页面切换刷新的体验好一些,由于:
优势:
减轻服务端压力
按需请求,每次只需加载页面的部份内容,而不用重复加载一些公共的资源文件和不变的页面结构,大大减少了数据请求量,以减轻对服务器的带宽和性能压力,还大大提高了页面的加载速度。
优化页面跳转体验
常规页面跳转须要从新加载画面上的内容,会有明显的闪烁,并且每每和跳转前的页面没有连贯性,用户体验不是很好。若是再赶上页面比较庞大、网速又不是很好的状况,用户体验就更加雪上加霜了。使用pjax后,因为只刷新部分页面,切换效果更加流畅,并且能够定制过分动画,在等待页面加载的时候体验就比较舒服了。
缺点:
不支持一些低版本的浏览器(如IE系列)
pjax使用了pushState来改变地址栏的url,这是html5中history的新特性,在某些旧版浏览器中可能不支持。不过pjax会进行判断,功能不适用的时候会执行默认的页面跳转操做。
使服务端处理变得复杂
要作到普通请求返回完整页面,而pjax请求只返回部分页面,服务端就须要作一些特殊处理,固然这对于设计良好的后端框架来讲,添加一些统一处理仍是比较容易的,天然也没太大问题。另外,即便后台不作处理,设置pjax的fragment参数来达到一样的效果。
综合来看,pajx的优势很强势,缺点也几乎能够忽略,仍是很是值得推荐的,尤为是相似博客这种大部分状况下只有主体内容变化的网站。关键它使用简单、学习成本小,即时全站只有极个别页面能用获得,尝试下没什么损失。pjax的github主页介绍的已经很详细了,想了解更多能够看下源码。
客户端设置分两步:
a
标签跳转。下面代码表示:当selector
被点击时,执行ajax请求,并将返回的HTML字符串填充在container
标记的位置。
$(document).pjax(selector, [container], options)
参数说明
pjax参数配置
key | default | description |
---|---|---|
timeout |
650 | ajax请求若是超时将触发强制刷新 |
push |
true | 使用 [pushState][] 在浏览器中添加导航记录 |
replace |
false | 是否使用replace方式改变URL |
maxCacheLength |
20 | 返回的HTML片断字符串最大缓存数 |
version |
当前pjax版本 | |
scrollTo |
0 | 当页面导航切换时滚动到的位置. 若是想页面切换不作滚动重置处理,请传入false . |
type |
"GET" |
使用ajax的模板请求方法,参考 $.ajax |
dataType |
"html" |
模板请求时的type,参考 $.ajax |
container |
内容替换的CSS选择器 | |
url |
link.href | 用于ajax请求的url,能够是字符串或者返回字符串的函数 |
target |
link | eventually the relatedTarget value for pjax events |
fragment |
从服务端返回的HTML字符串中子内容所在的CSS选择器,用于当服务端返回了整个HTML文档,但要求pjax局部刷新时使用。 |
可使用下面的方式动态设置options:
$.pjax.defaults.timeout = 1200
事件
pjax:click
和 pjax:clicked
的事件源是点击的按钮,其余事件的事件源都是要替换内容的容器。能够在 pjax:start
事件触发时开始过分动画,在 pjax:end
事件触发时结束过分动画。事件名 | 支持取消 | 参数 | 说明 |
---|---|---|---|
pjax:click | ✔ |
options | 点击按钮时触发。可调用 e.preventDefault(); 取消pjax |
pjax:beforeSend | ✔ |
xhr, options | ajax 执行 beforeSend 函数时触发,可在回调函数中设置额外的请求头参数。可调用 e.preventDefault(); 取消 pjax |
pjax:start | xhr, options | pjax 开始(与服务器链接创建后触发) |
|
pjax:send | xhr, options | pjax:start 以后触发 |
|
pjax:clicked | options | ajax 请求开始后触发 |
|
pjax:beforeReplace | contents, options | ajax 请求成功,内容替换渲染前触发 |
|
pjax:success | data, status, xhr, options | 内容替换成功后触发 | |
pjax:timeout | ✔ |
xhr, options | ajax请求超时后触发。可调用 e.preventDefault(); 继续等待 ajax 请求结束 |
pjax:error | ✔ |
xhr, textStatus, error, options | ajax 请求失败后触发。默认失败后会跳转url,如要阻止跳转可调用 e.preventDefault(); |
pjax:complete | xhr, textStatus, options | ajax 请求结束后触发,无论成功仍是失败 |
|
pjax:end | xhr, options | pjax 全部事件结束后触发 |
pjax:beforeReplace
事件前 pjax
会调用 extractContainer
函数处理页面内容,即以 script[src]
的形式引入的 js
脚本不会被重复加载,有必要能够改下源码。事件名 | 参数 | 说明 |
---|---|---|
pjax:popstate | 页面导航方向: 'forward'/'back'(前进/后退) | |
pjax:start | null, options | pjax 开始 |
pjax:beforeReplace | contents, options | 内容替换渲染前触发,若是缓存了要导航页面的内容则使用缓存,不然使用 pjax 加载 |
pjax:end | null, options | pjax 结束 |
初始化通常的作法是作好HTML结构,有条件的触发pjax跳转请求:
<div data-pjax> <a data-pjax href="/to/somewhere">ToSomewhere1</a> <a data-pjax href="/to/somewhere">ToSomewhere2/a> </div> <section id="pjax-container"> <!-- 在这里更新返回的HTML字符串 --> </section> $(document).pjax('[data-pjax] a, a[data-pjax]', '#pjax-container')
/** * 方式一 按钮父节点监听事件 * * @param selector 触发点击事件的按钮 * @param container 展现刷新内容的容器,也就是会被替换的部分 * @param options 参数 */ $(document).pjax(selector, [container], options); // 方式二 直接对按钮监听,能够不用指定容器,使用按钮的data-pjax属性值查找容器 $("a[data-pjax]").pjax(); // 方式三 常规的点击事件监听方式 $(document).on('click', 'a', $.pjax.click); $(document).on('click', 'a', function(event) { var container = $(this).closest('[data-pjax-container]'); $.pjax.click(event, container); }); // 下列是源码中介绍的其余用法,因为本人暂时没有那些需求暂时没深究,有兴趣的各位本身试试看哈 // 表单提交 $(document).on('submit', 'form', function(event) { var container = $(this).closest('[data-pjax-container]'); $.pjax.submit(event, container); }); // 加载内容到指定容器 $.pjax({ url: this.href, container: '#main' }); // 从新当前页面容器的内容 $.pjax.reload('#container');
服务端也比较简单,监听HTTP的header中有X-PJAX
的ajax请求,若是有则返回HTML片断,而不是整个HTML。
示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Pjax</title> </head> <body> <a href="page3.html" data-pjax>页面三</a> | <a href="page4.html">页面四</a> | <div id="container"> </div> <script src="../../js/jquery-1.12.4/jquery-1.12.4.js"></script> <script src="../../js/pjax/jquery.pjax.js"></script> <script> $(document).pjax("a","#container",{}); </script> </body> </html>
模拟服务器端:
page3.html:
<div style="background:lightcoral"> <h2>这是页面三</h2> </div>
page4.html:
<div style="background:lightseagreen"> <h2>这是页面四</h2> </div>
运行结果:
解释:$(document).pjax('a', '#container')其中a是触发元素,#container是装载pjax返回内容的容器,下面也是这样。
客户端:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Pjax</title> </head> <body> <button data-href="page3.html">页面三</button> <button data-href="page4.html">页面四</button> <div id="container"> </div> <script src="../../js/jquery-1.12.4/jquery-1.12.4.js"></script> <script src="../../js/pjax/jquery.pjax.js"></script> <script> $("button[data-href]").click(function () { $.pjax({ container: "#container", url: $(this).data("href") }); }); </script> </body> </html>
运行结果:
这部分用于更细粒度的控制。
示例:
// 肯定能使用pjax时 if ($.support.pjax) { $(document).on('click', 'a[data-pjax]', function(event) { var container = $(this).closest('[data-pjax-container]') var containerSelector = '#' + container.id $.pjax.click(event, {container: containerSelector}) }) }
用pjax提交表单
$(document).on('submit', 'form[data-pjax]', function(event) { $.pjax.submit(event, '#pjax-container') })
对当前URL使用pjax的方式从新获取HTML代码片断,而且在指定容器替换,这个过程不添加新的历史记录。(子片断重刷新)
$.pjax.reload('#pjax-container', options)
不是经过click
触发pjax的时候使用。好比某些操做后自动触发pjax的过程。若是能获取到click
的event
事件时,建议使用$.pjax-click(event)
替换。
function applyFilters() { var url = urlForFilters() $.pjax({url: url, container: '#pjax-container'}) }
pjax生命周期简单的说:
生命周期和Loading组件使用密切:
$(document).on('pjax:send', function() { $('#loading').show() }) $(document).on('pjax:complete', function() { $('#loading').hide() })
pjax只是请求HTML片断以后插入指定位置,所以片断内的JS插件/组件初始化须要在pjax:end
事件后执行。
$(document).on('ready pjax:end', function(event) { $(event.target).initializeMyPlugin() })
这段代码会在document ready或者container ready后执行initializeMyPlugin
初始化方法(包括前进后退)。
当使用pjax致使整个页面被强制刷时,可能的缘由是:
<html>
标签且fragment
选择器没有指定时。若是指定了fragment
选择器,pjax将从HTML文档中提取须要局部刷新的子片断。在响应头中设置X-PJAX-URL
,例如:
request.headers['X-PJAX-URL'] = "http://example.com/hello"
当客户端页面的pjax版本和服务器返回的pjax版本不一致时,页面会从新刷新。
客户端页面的pjax版本:
<meta http-equiv="x-pjax-version" content="v123">
若是服务器修改了版本则从新刷新:
response.headers['X-PJAX-Version'] = "xxxx修改版本名称xxxx"
这货须要服务端密切配合,若是服务端没设置好,要不就是请求只返回HTML片断,要不每次页面切换都是从新加载页面。
若是服务端没法完成这些配置,只能ajax异步由前端本身拼接HTML来作,建议使用MV*的库来作这部分。
插件伴侣——NProgress
官网:http://ricostacruz.com/nprogress/
github:https://github.com/rstacruz/nprogress/
比较漂亮的一款进度条插件,用法十分简单,很适合作pjax的过分动画,详细用法在该项目github上有介绍
Vue Router是一个Vue核心插件,是Vue.js官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超连接来实现页面切换和跳转的。在vue router单页面应用中,则是路径之间的切换,也就是组件的切换。包含的功能有:
中文帮助:https://router.vuejs.org/zh/
英文帮助:https://router.vuejs.org/
Git源码:https://github.com/vuejs/vue-router
路由中有三个基本的概念 route, routes, router。
一、 route,它是一条路由,由这个英文单词也能够看出来,它是单数, Home按钮 => home内容, 这是一条route, about按钮 => about 内容, 这是另外一条路由。
二、 routes 是一组路由,把上面的每一条路由组合起来,造成一个数组。[{home 按钮 =>home内容 }, { about按钮 => about 内容}]
三、router 是一个机制,至关于一个管理者,它来管理路由。由于routes 只是定义了一组路由,它放在哪里是静止的,当真正来了请求,怎么办? 就是当用户点击home 按钮的时候,怎么办?这时router 就起做用了,它到routes 中去查找,去找到对应的 home 内容,因此页面中就显示了 home 内容。
四、客户端中的路由,实际上就是dom 元素的显示和隐藏。当页面中显示home 内容的时候,about 中的内容所有隐藏,反之也是同样。客户端路由有两种实现方式:基于hash 和基于html5 history api。
SPA(single page application):单一页面应用程序,有且只有一个完整的页面;当它在加载页面的时候,不会加载整个页面的内容,而只更新某个指定的容器中内容。
单页面应用(SPA)的核心之一是:
1.更新视图而不从新请求页面;
2.vue-router在实现单页面前端路由时,提供了三种方式:Hash模式、History模式、abstract模式,根据mode参数来决定采用哪种方式。
路由模式
vue-router 提供了三种运行模式:
● hash: 使用 URL hash 值来做路由,默认模式。
● history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式。
● abstract: 支持全部 JavaScript 运行环境,如 Node.js 服务器端。
https://unpkg.com/vue-router/dist/vue-router.js
Unpkg.com 提供了基于 NPM 的 CDN 连接。上面的连接会一直指向在 NPM 发布的最新版本。你也能够像 https://unpkg.com/vue-router@2.0.0/dist/vue-router.js 这样指定 版本号 或者 Tag。
在 Vue 后面加载 vue-router,它会自动安装的:
<script src="/path/to/vue.js"></script> <script src="/path/to/vue-router.js"></script>
页面中直接引用CDN
<script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
使用nodejs包管理器安装
npm install vue-router
若是在一个模块化工程中使用它,必需要经过 Vue.use() 明确地安装路由功能:
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter)
若是使用全局的 script 标签,则无须如此 (手动安装)。
若是你想使用最新的开发版,就得从 GitHub 上直接 clone,而后本身 build 一个 vue-router。
git clone https://github.com/vuejs/vue-router.git node_modules/vue-router cd node_modules/vue-router npm install npm run build
用 Vue.js + Vue Router 建立单页应用,是很是简单的。使用 Vue.js ,咱们已经能够经过组合组件来组成应用程序,当你要把 Vue Router 添加进来,咱们须要作的是,将组件 (components) 映射到路由 (routes),而后告诉 Vue Router 在哪里渲染它们。下面是个基本例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello Router App</title> </head> <body> <div id="app"> <h1>Hello Router App!</h1> <p> <!-- 使用 router-link 组件来导航. --> <!-- 经过传入 `to` 属性指定连接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> <p> <a href="#/foo">foo</a> | <a href="#/bar">bar</a> </p> </p> <!-- 路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div> <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <script> // 0. 若是使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter) // 1. 定义 (路由) 组件。 // 能够从其余文件 import 进来 const Foo = {template: '<div>foo</div>'} const Bar = {template: '<div>bar</div>'} // 2. 定义路由 // 每一个路由应该映射一个组件。 其中"component" 能够是 // 经过 Vue.extend() 建立的组件构造器, // 或者,只是一个组件配置对象。 // 咱们晚点再讨论嵌套路由。 const routes = [ {path: '/foo', component: Foo}, {path: '/bar', component: Bar} ] // 3. 建立 router 实例,而后传 `routes` 配置 // 你还能够传别的配置参数, 不过先这么简单着吧。 const router = new VueRouter({ routes // (缩写) 至关于 routes: routes }) // 4. 建立和挂载根实例。 // 记得要经过 router 配置参数注入路由, // 从而让整个应用都有路由功能 const app = new Vue({ el: "#app", router: router, }); </script> </body> </html>
运行结果:
安装:
npm install vue-router / yarn add vue-router
若在构建vue-cli的时候,在询问“nstall vue-router”(是否安装vue-router)时,选择“Y”,这里就不用重复安装vue-router。使用WebStorm建立一个vue-cli项目,选择使用router:
在src/components下建立三个组件:
Default.vue
<template> <div> <h2>Default</h2> <p>{{msg}}</p> </div> </template> <script> export default { data() { return { msg: "这是默认组件" } } } </script> <style scoped> h2 { color:crimson; } </style>
Home.vue
<template> <div> <h2>Home</h2> <p>{{msg}}</p> </div> </template> <script> export default { data() { return { msg: "我是Home 组件" } } } </script> <style scoped> h2 { color: dodgerblue; } </style>
About.Vue
<template> <div> <h2>About</h2> <p>{{msg}}</p> </div> </template> <script> export default { data() { return { msg: "我是About 组件" } } } </script> <style scoped> h2 { color:springgreen; } </style>
在 App.vue中 定义<router-link > 和 </router-view>
<template> <div> <img src="./assets/logo.png"> <header> <!-- router-link 定义点击后导航到哪一个路径下 --> <router-link to="/">Default</router-link> <router-link to="/home">Home</router-link> <router-link to="/about">About</router-link> </header> <!-- 对应的组件内容渲染到router-view中 --> <router-view></router-view> </div> </template> <script> export default { } </script>
在src/router目录下定义hello.js路由配置文件:
import Vue from 'vue' import Router from 'vue-router' import Home from '@/components/Home' import About from '@/components/About' import Default from '@/components/Default' Vue.use(Router); export default new Router({ routes: [ { path: '/', name: 'Default', component: Default }, { path: '/home', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About } ] })
修改main.js,使用路由:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router/hello' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, render:r=>r(App) })
index.html页面以下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Hello Router</title> </head> <body> <div id="app"> </div> </body> </html>
目录结构以下:
运行结果:
单页切换
vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,因而当 URL 改变时,页面不会从新加载
http://localhost:8080/#/home
若是不想要很hash,能够用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须从新加载页面
const router = new VueRouter({ mode: 'history', routes: [...] }
当使用 history 模式时,URL 就像正常的 url
http://localhost:8080/home
不过这种模式须要后台配置支持。若是后台没有正确的配置,当用户在浏览器直接访问 http://site.com/user/id 就会返回 404,详细请参考:https://router.vuejs.org/zh/guide/essentials/history-mode.html
重定向经过 routes
配置来完成,下面例子是从 /a
重定向到 /b
const router = new VueRouter({ routes: [ { path: '/a', redirect: '/b' } ] })
重定向的目标也能够是一个命名的路由:
const router = new VueRouter({ routes: [ { path: '/a', redirect: { name: 'foo' }} ] })
甚至是一个方法,动态返回重定向目标:
const router = new VueRouter({ routes: [ { path: '/a', redirect: to => { // 方法接收 目标路由 做为参数 // return 重定向的 字符串路径/路径对象
return '/home' }} ] })
对于不识别的URL地址来讲,经常使用重定向功能,将页面定向到首页显示
const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar }, { path: '*', redirect: "/foo"}, ]
示例:
import Vue from 'vue' import Router from 'vue-router' import Home from '@/components/Home' import About from '@/components/About' import Default from '@/components/Default' Vue.use(Router); export default new Router({ mode: "history", routes: [ { path: '/', name: 'Default', component: Default }, { path: '/home', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About }, { path: '/gohome', redirect:'/home' /*路径*/ }, { path: '/goabout', redirect:'About' /*命名*/ } ] })
结果:
重定向是指,当用户访问 /a
时,URL 将会被替换成 /b
,而后匹配路由为 /b
,那么别名是什么呢?/a
的别名是 /b
,意味着,当用户访问 /b
时,URL 会保持为 /b
,可是路由匹配则为 /a
,就像用户访问 /a
同样
上面对应的路由配置为
const router = new VueRouter({ routes: [ { path: '/a', component: A, alias: '/b' } ] })
『别名』的功能能够自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构
处理首页访问时,经常将index设置为别名,好比将'/home'的别名设置为'/index'。可是,要注意的是,<router-link to="/home">的样式在URL为/index时并不会显示。由于,router-link只识别出了home,而没法识别index
示例:
配置
import Vue from 'vue' import Router from 'vue-router' import Home from '@/components/Home' import About from '@/components/About' import Default from '@/components/Default' Vue.use(Router); export default new Router({ mode: "history", routes: [ { path: '/', name: 'Default', component: Default }, { path: '/home', name: 'Home', component: Home, alias:'/h' /*别名*/ }, { path: '/about', name: 'About', component: About }, { path: '/gohome', redirect:'/home' /*路径,重定向*/ }, { path: '/goabout', redirect:'About' /*命名,重定向*/ } ] })
连接:
<template> <div> <img src="./assets/logo.png"> <header> <!-- router-link 定义点击后导航到哪一个路径下 --> <router-link to="/">Default</router-link> | <router-link to="/home">Home</router-link> | <router-link to="/about">About</router-link> | <router-link to="/gohome">GoHome</router-link> | <router-link to="/goabout">GoAbout</router-link> | <router-link to="/h">H</router-link> </header> <!-- 对应的组件内容渲染到router-view中 --> <router-view></router-view> </div> </template> <script> export default { } </script> <style scoped> a{ color: #777; } a:hover{ color:orangered; } </style>
结果:
设置根路径,须要将path设置为'/'
<p> <router-link to="/">index</router-link> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> const routes = [ { path: '/', component: Home }, { path: '/foo', component: Foo }, { path: '/bar', component: Bar }, ]
默认运行效果:
点击Go to Bar
可是,因为默认使用的是全包含匹配,即'/foo'、'/bar'也能够匹配到'/',若是须要精确匹配,仅仅匹配'/',则须要在router-link中设置exact属性
<p> <router-link to="/" exact>index</router-link> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> const routes = [ { path: '/', component: Home }, { path: '/foo', component: Foo }, { path: '/bar', component: Bar }, ]
运行结果:
实际项目中的应用界面,一般由多层嵌套的组件组合而成。一样地,URL中各段动态路径也按某种结构对应嵌套的各层组件
借助 vue-router
,使用嵌套路由配置,就能够很简单地表达这种关系
<div id="app"> <p> <router-link to="/" exact>index</router-link> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> <router-view></router-view> </div>
const Home = { template: '<div>home</div>' } const Foo = { template: ` <div> <p> <router-link to="/foo/foo1">to Foo1</router-link> <router-link to="/foo/foo2">to Foo2</router-link> <router-link to="/foo/foo3">to Foo3</router-link> </p> <router-view></router-view> </div> ` } const Bar = { template: '<div>bar</div>' } const Foo1 = { template: '<div>Foo1</div>' } const Foo2 = { template: '<div>Foo2</div>' } const Foo3 = { template: '<div>Foo3</div>' }
const routes = [ { path: '/', component: Home }, { path: '/foo', component: Foo ,children:[ {path:'foo1',component:Foo1}, {path:'foo2',component:Foo2}, {path:'foo3',component:Foo3}, ]}, { path: '/bar', component: Bar }, ]
要特别注意的是,router的构造配置中,children属性里的path属性只设置为当前路径,由于其会依据层级关系;而在router-link的to属性则须要设置为彻底路径
若是要设置默认子路由,即点击foo时,自动触发foo1,则须要进行以下修改。将router配置对象中children属性的path属性设置为'',并将对应的router-link的to属性设置为'/foo'
const Foo = { template: `
<div>
<p>
<router-link to="/foo" exact>to Foo1</router-link>
<router-link to="/foo/foo2">to Foo2</router-link>
<router-link to="/foo/foo3">to Foo3</router-link>
</p>
<router-view></router-view>
</div>
` }
const routes = [ { path: '/', component: Home }, { path: '/foo', component: Foo ,children:[ {path:'',component:Foo1}, {path:'foo2',component:Foo2}, {path:'foo3',component:Foo3}, ]}, { path: '/bar', component: Bar }, ]
Foo1.Vue
<template> <div> <h2>这是Foo1</h2> </div> </template> <script> export default { name: "Foo1" } </script> <style scoped> h2 { color: purple; } </style>
Foo2.Vue
<template> <div> <h2>这是Foo2</h2> </div> </template> <script> export default { name: "Foo2" } </script> <style scoped> h2 { color: orange; } </style>
Foo3.Vue
<template> <div> <h2>这是Foo3</h2> </div> </template> <script> export default { name: "Foo3" } </script> <style scoped> h2 { color:springgreen; } </style>
Foo.Vue
<template> <div> <h2>Foo</h2> <p>{{msg}}</p> <div> <p> <router-link to="/foo/foo1">to Foo1</router-link> <router-link to="/gofoo2">to Foo2</router-link> <router-link to="/foo/foo3">to Foo3</router-link> </p> <div> <router-view></router-view> </div> </div> </div> </template> <script> export default { data() { return { msg: "我是Foo组件" } } } </script> <style scoped> h2 { color: dodgerblue; } </style>
hello.js路由配置
import Vue from 'vue' import Router from 'vue-router' import Foo from '@/components/Foo' import Bar from '@/components/Bar' import Home from '@/components/Home' import Foo1 from '@/components/Foo1' import Foo2 from '@/components/Foo2' import Foo3 from '@/components/Foo3' Vue.use(Router); export default new Router({ mode: "history", routes: [ { path: '/', name: 'Home', component: Home }, { path: '/foo', name: 'Foo', component: Foo, children:[ {path: '/foo/foo1', component: Foo1}, {path: '/gofoo2', component: Foo2}, {path: 'foo3', component: Foo3}, ] }, { path: '/bar', name: 'Bar', component: Bar } ] })
运行结果:
默认
Foo3
有时,经过一个名称来标识一个路由显得更方便,特别是在连接一个路由,或者是执行一些跳转时。能够在建立Router实例时,在routes
配置中给某个路由设置名称
const router = new VueRouter({
routes: [
{
path: '/user/:userId', name: 'user', component: User } ] })
要连接到一个命名路由,能够给 router-link
的 to
属性传一个对象:
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
这跟代码调用 router.push()
是一回事
router.push({ name: 'user', params: { userId: 123 }})
这两种方式都会把路由导航到 /user/123
路径
命名路由的常见用途是替换router-link中的to属性,若是不使用命名路由,由router-link中的to属性须要设置全路径,不够灵活,且修改时较麻烦。使用命名路由,只须要使用包含name属性的对象便可
[注意]若是设置了默认子路由,则不要在父级路由上设置name属性
<div id="app"> <p> <router-link to="/" exact>index</router-link> <router-link :to="{ name: 'foo1' }">Go to Foo</router-link> <router-link :to="{ name: 'bar' }">Go to Bar</router-link> </p> <router-view></router-view> </div>
const Home = { template: '<div>home</div>' } const Foo = { template: ` <div> <p> <router-link :to="{ name: 'foo1' }" exact>to Foo1</router-link> <router-link :to="{ name: 'foo2' }" >to Foo2</router-link> <router-link :to="{ name: 'foo3' }" >to Foo3</router-link> </p> <router-view></router-view> </div> ` } const Bar = { template: '<div>bar</div>' } const Foo1 = { template: '<div>Foo1</div>' } const Foo2 = { template: '<div>Foo2</div>' } const Foo3 = { template: '<div>Foo3</div>' }
const routes = [
{ path: '/', name:'home', component: Home },
{ path: '/foo', component: Foo ,children:[
{path:'',name:'foo1', component:Foo1},
{path:'foo2',name:'foo2', component:Foo2},
{path:'foo3',name:'foo3', component:Foo3},
]},
{ path: '/bar', name:'bar', component: Bar },
]
hello.js
children:[ {name:'f0',path: '', component: Foo1}, {name:'f1',path: '/foo/foo1', component: Foo1}, {name:'f2',path: '/gofoo2', component: Foo2}, {name:'f3',path: 'foo3', component: Foo3} ]
Foo.vue
<p> <router-link to="/foo" exact>to Default</router-link> <router-link :to="{name:'f1'}">to Foo1</router-link> <router-link to="/gofoo2">to Foo2</router-link> <router-link to="/foo/foo3">to Foo3</router-link> </p>
结果以下所示
有时候想同时(同级)展现多个视图,而不是嵌套展现,例如建立一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。能够在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。若是 router-view
没有设置名字,那么默认为 default
<router-view class="view one"></router-view> <router-view class="view two" name="a"></router-view> <router-view class="view three" name="b"></router-view>
一个视图使用一个组件渲染,所以对于同个路由,多个视图就须要多个组件。确保正确使用components
配置
const router = new VueRouter({
routes: [
{
path: '/', components: { default: Foo, a: Bar, b: Baz } } ] })
下面是一个实例
<div id="app"> <p> <router-link to="/" exact>index</router-link> <router-link :to="{ name: 'foo' }">Go to Foo</router-link> <router-link :to="{ name: 'bar' }">Go to Bar</router-link> </p> <router-view></router-view> <router-view name="side"></router-view> </div>
const Home = { template: '<div>home</div>' } const Foo = { template: '<div>Foo</div>'} const MainBar = { template: '<div>mainBar</div>' } const SideBar = { template: '<div>sideBar</div>' } const routes = [ { path: '/', name:'home', component: Home }, { path: '/foo', name:'foo', component: Foo}, { path: '/bar', name:'bar', components: { default: MainBar, side:SideBar } }, ]
结果以下所示
常常须要把某种模式匹配到的全部路由,全都映射到同个组件。例如,有一个 User
组件,对于全部 ID 各不相同的用户,都要使用这个组件来渲染。那么,能够在 vue-router
的路由路径中使用动态路径参数(dynamic segment)来达到这个效果
const User = { template: '<div>User</div>' } const router = new VueRouter({ routes: [ // 动态路径参数以冒号开头 { path: '/user/:id', component: User } ] })
如今,像 /user/foo
和 /user/bar
都将映射到相同的路由
下面是一个比较完整的实例,path:'/user/:id?'表示有没有子路径均可以匹配
<div id="app"> <router-view></router-view> <br> <p> <router-link to="/" exact>index</router-link> <router-link :to="{name:'user'}">User</router-link> <router-link :to="{name:'bar'}">Go to Bar</router-link> </p> </div> const home = { template: '<div>home</div>'}; const bar = { template: '<div>bar</div>'}; const user = {template: `<div> <p>user</p> <router-link style="margin: 0 10px" :to="'/user/' + item.id" v-for="item in userList" key="item.id">{{item.userName}}</router-link> </div>`, data(){ return{userList:[{id:1,userName:'u1'},{id:2,userName:'u2'},{id:3,userName:'u3'}]} } }; const app = new Vue({ el:'#app', router:new VueRouter({ routes: [ { path: '/', name:'home', component:home }, { path: '/user/:id?', name:'user', component:user}, { path: '/bar', name:'bar', component:bar}, ], }), })
一个路径参数使用冒号 :
标记。当匹配到一个路由时,参数值会被设置到 this.$route.params
,能够在每一个组件内使用。因而,能够更新 User
的模板,输出当前用户的 ID:
const User = { template: '<div>User {{ $route.params.id }}</div>' }
下面是一个实例
<div id="app"> <p> <router-link to="/user/foo">/user/foo</router-link> <router-link to="/user/bar">/user/bar</router-link> </p> <router-view></router-view> </div> <script src="vue.js"></script> <script src="vue-router.js"></script> <script> const User = { template: `<div>User {{ $route.params.id }}</div>` } const router = new VueRouter({ routes: [ { path: '/user/:id', component: User } ] }) const app = new Vue({ router }).$mount('#app') </script>
能够在一个路由中设置多段『路径参数』,对应的值都会设置到 $route.params
中。例如:
模式 匹配路径 $route.params /user/:username /user/evan { username: 'evan' } /user/:username/post/:post_id /user/evan/post/123 { username: 'evan', post_id: 123 }
除了 $route.params
外,$route
对象还提供了其它有用的信息,例如,$route.query
(若是 URL 中有查询参数)、$route.hash
等等
【响应路由参数的变化】
使用路由参数时,例如从 /user/foo
导航到 user/bar
,原来的组件实例会被复用。由于两个路由都渲染同个组件,比起销毁再建立,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用
复用组件时,想对路由参数的变化做出响应的话,能够简单地 watch(监测变化) $route
对象:
const User = { template: '...', watch: { '$route' (to, from) { // 对路由变化做出响应... } } }
[注意]有时同一个路径能够匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高
下面是一个实例
const home = { template: '<div>home</div>'}; const bar = { template: '<div>bar</div>'}; const user = {template: `<div> <p>user</p> <router-link style="margin: 0 10px" :to="'/user/' +item.type + '/'+ item.id" v-for="item in userList" key="item.id">{{item.userName}}</router-link> <div v-if="$route.params.id"> <div>id:{{userInfo.id}};userName:{{userInfo.userName}} ;type:{{userInfo.type}};</div> </div> </div>`, data(){ return{ userList:[{id:1,type:'vip',userName:'u1'},{id:2,type:'common',userName:'u2'},{id:3,type:'vip',userName:'u3'}], userInfo:null, } }, methods:{ getData(){ let id = this.$route.params.id; if(id){ this.userInfo = this.userList.filter((item)=>{ return item.id == id; })[0] }else{ this.userInfo = {}; } } }, created(){ this.getData(); }, watch:{ $route(){ this.getData(); }, } }; const app = new Vue({ el:'#app', router:new VueRouter({ routes: [ { path: '/', name:'home', component:home }, { path: '/user/:type?/:id?', name:'user', component:user}, { path: '/bar', name:'bar', component:bar}, ], }), })
实现子路由,除了使用动态参数,也可使用查询字符串
const home = { template: '<div>home</div>'}; const bar = { template: '<div>bar</div>'}; const user = {template: `<div> <p>user</p> <router-link style="margin: 0 10px" :to="'/user/' +item.type + '/'+ item.id" v-for="item in userList" key="item.id">{{item.userName}}</router-link> <div v-if="$route.params.id"> <div>id:{{userInfo.id}};userName:{{userInfo.userName}} ;type:{{userInfo.type}};</div> <router-link to="?info=follow" exact>关注</router-link> <router-link to="?info=share" exact>分享</router-link> </div> </div>`, data(){ return{ userList:[{id:1,type:'vip',userName:'u1'},{id:2,type:'common',userName:'u2'},{id:3,type:'vip',userName:'u3'}], userInfo:null, } }, methods:{ getData(){ let id = this.$route.params.id; if(id){ this.userInfo = this.userList.filter((item)=>{ return item.id == id; })[0] }else{ this.userInfo = {}; } } }, created(){ this.getData(); }, watch:{ $route(){ this.getData(); }, } }; const app = new Vue({ el:'#app', router:new VueRouter({ routes: [ { path: '/', name:'home', component:home }, { path: '/user/:type?/:id?', name:'user', component:user}, { path: '/bar', name:'bar', component:bar}, ], }), })
当须要设置默认查询字符串时,进行以下设置
const user = {template: `<div> <p>user</p> <router-link style="margin: 0 10px" :to="{path:'/user/' +item.type + '/'+ item.id,query:{info:'follow'}}" v-for="item in userList" key="item.id">{{item.userName}}</router-link> <div v-if="$route.params.id"> <div>id:{{userInfo.id}};userName:{{userInfo.userName}} ;type:{{userInfo.type}};</div> <router-link to="?info=follow" exact>关注</router-link> <router-link to="?info=share" exact>分享</router-link> {{$route.query}} </div> </div>`, data(){ return{ userList:[{id:1,type:'vip',userName:'u1'},{id:2,type:'common',userName:'u2'},{id:3,type:'vip',userName:'u3'}], userInfo:null, } }, methods:{ getData(){ let id = this.$route.params.id; if(id){ this.userInfo = this.userList.filter((item)=>{ return item.id == id; })[0] }else{ this.userInfo = {}; } } }, created(){ this.getData(); }, watch:{ $route(){ this.getData(); }, } };
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像从新加载页面那样。 vue-router
能作到,并且更好,它能够自定义路由切换时页面如何滚动
[注意]这个功能只在 HTML5 history 模式下可用
当建立一个 Router 实例,能够提供一个 scrollBehavior
方法。该方法在前进、后退或切换导航时触发
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 指望滚动到哪一个的位置
}
})
scrollBehavior
方法返回 to
和 from
路由对象。第三个参数 savedPosition
当且仅当 popstate
导航 (经过浏览器的 前进/后退 按钮触发) 时才可用,返回滚动条的坐标{x:number,y:number}
若是返回一个布尔假的值,或者是一个空对象,那么不会发生滚动
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
对于全部路由导航,简单地让页面滚动到顶部。返回 savedPosition
,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
下面是一个实例,点击导航进行切换时,滚动到页面顶部;经过前进、后退按钮进行切换时,保持坐标位置
const router = new VueRouter({ mode:'history', routes , scrollBehavior (to, from, savedPosition){ if(savedPosition){ return savedPosition; }else{ return {x:0,y:0} } } })
还能够模拟『滚动到锚点』的行为:
scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return {
selector: to.hash
}
}
}
下面是一个实例
<div id="app"> <router-view></router-view> <br> <p> <router-link to="/" exact>index</router-link> <router-link :to="{name:'foo' ,hash:'#abc'}">Go to Foo</router-link> <router-link :to="{ name: 'bar' }">Go to Bar</router-link> </p> </div>
const router = new VueRouter({ mode:'history', routes , scrollBehavior (to, from, savedPosition){ if(to.hash){ return { selector: to.hash } } if(savedPosition){ return savedPosition; }else{ return {x:0,y:0} } } })
<router-view>
是基本的动态组件,因此能够用 <transition>
组件给它添加一些过渡效果:
<transition> <router-view></router-view> </transition>
下面是一个实例
.router-link-active{background:pink;} .v-enter,.v-leave-to{ opacity:0; } .v-enter-active,.v-leave-active{ transition:opacity .5s; }
<div id="app"> <p> <router-link to="/" exact>index</router-link> <router-link :to="{name:'foo'}">Go to Foo</router-link> <router-link :to="{ name: 'bar' }">Go to Bar</router-link> <transition> <router-view></router-view> </transition> </p> </div>
【单个路由过渡】
上面的用法会给全部路由设置同样的过渡效果,若是想让每一个路由组件有各自的过渡效果,能够在各路由组件内使用 <transition>
并设置不一样的 name
const Foo = {
template: `
<transition name="slide">
<div class="foo">...</div>
</transition>
` } const Bar = { template: ` <transition name="fade"> <div class="bar">...</div> </transition> ` }
定义路由的时候能够配置 meta
字段:
const router = new VueRouter({
routes: [
{
path: '/foo', component: Foo, children: [ { path: 'bar', component: Bar, meta: { requiresAuth: true } } ] } ] })
routes
配置中的每一个路由对象被称为路由记录。路由记录能够是嵌套的,所以,当一个路由匹配成功后,它可能匹配多个路由记录。例如,根据上面的路由配置,/foo/bar
这个URL将会匹配父路由记录以及子路由记录
一个路由匹配到的全部路由记录会暴露为 $route
对象(还有在导航钩子中的 route 对象)的 $route.matched
数组。所以,须要遍历 $route.matched
来检查路由记录中的 meta
字段
下面例子展现在全局导航钩子中检查 meta 字段:
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) { if (!auth.loggedIn()) { next({ path: '/login', query: { redirect: to.fullPath } }) } else { next() } } else { next() } })
【基于路由的动态过渡】
能够基于当前路由与目标路由的变化关系,动态设置过渡效果。经过使用路由元信息,在每一个路由对象上设置一个index属性保存其索引值
<style> .router-link-active{background:pink;} .left-enter{ transform:translateX(100%); } .left-leave-to{ transform:translateX(-100%); } .left-enter-active,.left-leave-active{ transition:transform .5s; } .right-enter{ transform:translateX(-100%); } .right-leave-to{ transform:translateX(100%); } .right-enter-active,.right-leave-active{ transition:transform .5s; } </style>
<div id="app"> <p> <router-link to="/" exact>index</router-link> <router-link :to="{name:'foo'}">Go to Foo</router-link> <router-link :to="{ name: 'bar' }">Go to Bar</router-link> <transition :name="transitionName"> <router-view></router-view> </transition> </p> </div>
const app = new Vue({
el:'#app',
router,
data () {
return {
'transitionName': 'left'
}
},
watch: {
'$route' (to, from) {
this['transitionName'] = to.meta.index > from.meta.index ? 'right' : 'left';
}
},
})
除了使用<router-link>
建立a标签来定义导航连接,还能够借助router的实例方法,经过编写代码来实现
【router.push(location)】
想要导航到不一样的 URL,则使用 router.push
方法。这个方法会向 history 栈添加一个新的记录,因此,当用户点击浏览器后退按钮时,则回到以前的 URL。
当点击 <router-link>
时,这个方法会在内部调用,因此说,点击 <router-link :to="...">
等同于调用 router.push(...)
声明式 编程式
<router-link :to="..."> router.push(...)
在@click中,用$router表示路由对象,在methods方法中,用this.$router表示路由对象
该方法的参数能够是一个字符串路径,或者一个描述地址的对象。例如:
// 字符串 router.push('home') // 对象 router.push({ path: 'home' }) // 命名的路由 router.push({ name: 'user', params: { userId: 123 }}) // 带查询参数,变成 /register?plan=private router.push({ path: 'register', query: { plan: 'private' }})
【router.replace(location)
】
跟 router.push
很像,惟一的不一样就是,它不会向 history 添加新记录,而是跟它的方法名同样 —— 替换掉当前的 history 记录
声明式 编程式
<router-link :to="..." replace> router.replace(...)
【router.go(n)
】
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,相似 window.history.go(n)
// 在浏览器记录中前进一步,等同于 history.forward() router.go(1) // 后退一步记录,等同于 history.back() router.go(-1) // 前进 3 步记录 router.go(3) // 若是 history 记录不够用,就静默失败 router.go(-100) router.go(100)
【操做history】
router.push
、router.replace
和router.go
跟history.pushState、
history.replaceState
和history.go相似
, 实际上它们确实是效仿window.history
API的。vue-router的导航方法(push
、replace
、go
)在各种路由模式(history
、 hash
和abstract
)下表现一致
vue-router
提供的导航钩子主要用来拦截导航,让它完成跳转或取消。有多种方式能够在路由导航发生时执行钩子:全局的、单个路由独享的或者组件级的
【全局钩子】
可使用 router.beforeEach
注册一个全局的 before
钩子
const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
当一个导航触发时,全局的 before
钩子按照建立顺序调用。钩子是异步解析执行,此时导航在全部钩子 resolve 完以前一直处于 等待中。
每一个钩子方法接收三个参数:
to: Route: 即将要进入的目标路由对象
from: Route: 当前导航正要离开的路由
next: Function: 必定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
下面是next()函数传递不一样参数的状况
next(): 进行管道中的下一个钩子。若是所有钩子执行完了,则导航的状态就是 confirmed (确认的)。 next(false): 中断当前的导航。若是浏览器的 URL 改变了(多是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。 next('/') 或者 next({ path: '/' }): 跳转到一个不一样的地址。当前的导航被中断,而后进行一个新的导航。
[注意]确保要调用 next
方法,不然钩子就不会被 resolved。
一样能够注册一个全局的 after
钩子,不过它不像 before
钩子那样,after
钩子没有 next
方法,不能改变导航:
router.afterEach(route => { // ... })
下面是一个实例
const Home = { template: '<div>home</div>' } const Foo = { template: '<div>Foo</div>'} const Bar = { template: '<div>bar</div>' } const Login = { template: '<div>请登陆</div>' } const routes = [ { path: '/', name:'home', component: Home,meta:{index:0}}, { path: '/foo', name:'foo', component:Foo,meta:{index:1,login:true}}, { path: '/bar', name:'bar', component:Bar,meta:{index:2}}, { path: '/login', name:'login', component:Login,}, ] const router = new VueRouter({ routes , }) router.beforeEach((to, from, next) => { if(to.meta.login){ next('/login'); } next(); }); router.afterEach((to, from)=>{ document.title = to.name; }) const app = new Vue({ el:'#app', router, })
【单个路由独享】
能够在路由配置上直接定义 beforeEnter
钩子
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // ... } } ] })
这些钩子与全局 before
钩子的方法参数是同样的
【组件内钩子】
能够在路由组件内直接定义如下路由导航钩子
beforeRouteEnter beforeRouteUpdate (2.2 新增) beforeRouteLeave
const Foo = { template: `...`, beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用,不能获取组件实例 `this`,由于当钩子执行前,组件实例还没被建立 }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,可是该组件被复用时调用。举例来讲,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转时,因为会渲染一样的 Foo 组件,所以组件实例会被复用。而这个钩子就会在这个状况下被调用。能够访问组件实例 `this` }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用,能够访问组件实例 `this` } }
beforeRouteEnter
钩子不能访问this
,由于钩子在导航确认前被调用,所以即将登场的新组件还没被建立
不过,能够经过传一个回调给 next
来访问组件实例。在导航被确认的时候执行回调,而且把组件实例做为回调方法的参数
beforeRouteEnter (to, from, next) { next(vm => { // 经过 `vm` 访问组件实例 }) }
能够在 beforeRouteLeave
中直接访问 this
。这个 leave
钩子一般用来禁止用户在还未保存修改前忽然离开。能够经过 next(false)
来取消导航
有时候,进入某个路由后,须要从服务器获取数据。例如,在渲染用户信息时,须要从服务器获取用户的数据。能够经过两种方式来实现:
一、导航完成以后获取:先完成导航,而后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示『加载中』之类的指示
二、导航完成以前获取:导航完成前,在路由的 enter
钩子中获取数据,在数据获取成功后执行导航。从技术角度讲,两种方式都不错 —— 就看想要的用户体验是哪一种
【导航完成后获取】
当使用这种方式时,会立刻导航和渲染组件,而后在组件的 created
钩子中获取数据。有机会在数据获取期间展现一个 loading 状态,还能够在不一样视图间展现不一样的 loading 状态。
假设有一个 Post
组件,须要基于 $route.params.id
获取文章数据:
<template> <div class="post"> <div class="loading" v-if="loading"> Loading... </div> <div v-if="error" class="error"> {{ error }} </div> <div v-if="post" class="content"> <h2>{{ post.title }}</h2> <p>{{ post.body }}</p> </div> </div> </template> export default { data () { return { loading: false, post: null, error: null } }, created () { // 组件建立完后获取数据, // 此时 data 已经被 observed 了 this.fetchData() }, watch: { // 若是路由有变化,会再次执行该方法 '$route': 'fetchData' }, methods: { fetchData () { this.error = this.post = null this.loading = true // replace getPost with your data fetching util / API wrapper getPost(this.$route.params.id, (err, post) => { this.loading = false if (err) { this.error = err.toString() } else { this.post = post } }) } } }
【导航完成前获取数据】
经过这种方式,在导航转入新的路由前获取数据。能够在接下来的组件的 beforeRouteEnter
钩子中获取数据,当数据获取成功后只调用 next
方法
export default {
data () {
return {
post: null,
error: null
}
},
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
if (err) {
// display some global error message
next(false)
} else {
next(vm => {
vm.post = post
})
}
})
},
// 路由改变前,组件就已经渲染完了
// 逻辑稍稍不一样
watch: {
$route () {
this.post = null
getPost(this.$route.params.id, (err, post) => {
if (err) {
this.error = err.toString()
} else {
this.post = post
}
})
}
}
}
在为后面的视图获取数据时,用户会停留在当前的界面,所以建议在数据获取期间,显示一些进度条或者别的指示。若是数据获取失败,一样有必要展现一些全局的错误提醒
当打包构建应用时,JS包会变得很是大,影响页面加载。若是能把不一样路由对应的组件分割成不一样的代码块,而后当路由被访问的时候才加载对应组件,这样就更加高效了
结合 Vue 的 异步组件 和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
首先,能够将异步组件定义为返回一个 Promise 的工厂函数(该函数返回的Promise应该 resolve 组件自己)
const Foo = () => Promise.resolve({ /* 组件定义对象 */ })
在 webpack 2中,使用动态 import语法来定义代码分块点(split point):
import('./Foo.vue') // returns a Promise
[注意]若是使用的是 babel,须要添加 syntax-dynamic-import插件,才能使 babel 能够正确地解析语法
结合这二者,这就是如何定义一个可以被 webpack自动代码分割的异步组件
const Foo = () => import('./Foo.vue')
在路由配置中什么都不须要改变,只须要像往常同样使用 Foo
:
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo } ] })
【把组件按组分块】
有时候想把某个路由下的全部组件都打包在同个异步块(chunk)中。只须要使用 命名 chunk,一个特殊的注释语法来提供chunk name(须要webpack > 2.4)
const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue') const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue') const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中