路由是根據不一樣的 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 會將任何一個異步模塊與相同的塊名稱組合到相同的異步塊中