前端MVC Vue2學習總結(八)——前端路由

路由是根據不一樣的 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

1、Hjax(Hash + Ajax)

1.一、原理

原理: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>
View Code

運行結果:

這裏並無解決前進與後退失效的問題。

1.二、URL詳解

URL是統一資源定位符,對能夠從互聯網上獲得的資源的位置和訪問方法的一種簡潔的表示,是互聯網上標準資源的地址。互聯網上的每一個文件都有一個惟一的URL,它包含的信息指出文件的位置以及瀏覽器應該怎麼處理它。URI是統一資源標識符,而URL是統一資源定位符,咱們把URL理解爲是URI的一個子類,而另外一種子類是URN。url是統一資源定位符,對能夠從互聯網上獲得的資源的位置和訪問方法的一種簡潔的表示,是互聯網上標準資源的地址。互聯網上的每一個文件都有一個惟一的URL,它包含的信息指出文件的位置以及瀏覽器應該怎麼處理它。URL中全部的字符都是ASCII字符集,若是出現非ASCII字符集,好比中文,瀏覽器會先進行編碼再進行傳輸。

1.2.一、URL的構成

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

拆解以下:

1.2.二、用戶名與密碼

http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument

這裏用戶名是zhangguo,密碼是123456,若是帶@符用戶必須填寫,密碼選填,帶用戶名與密碼的狀況不多見,不少時候都放到了參數中了。

1.2.三、協議(Protocol)

http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument

http://爲協議名,標明瞭請求須要使用的協議,一般使用的是HTTP協議或者安全協議 HTTPS.其餘協議還有mailto:用戶打開郵箱的客戶端,和ftp:用來作文件的轉換, file用來獲取文件,data獲取外部資源等

1.2.四、域名(Domain)

http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument

www.example.com爲域名,標明瞭須要請求的服務器的地址,www是主機名,example是單位名稱,.com是機構類型

1.2.五、端口(Port)

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地址+端口號」來區分不一樣的服務.

若是把服務器比做房子,端口號能夠看作是通向不一樣服務的門,

1.2.六、文件路徑

http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument

/path/to/myfile.html表示服務器上資源的路徑,過去這樣的路徑標記的是服務器上文件的物理路徑,可是如今,路徑表示的只是一個抽象地址,並不指代任何物理地址.

1.2.七、參數(query、Parameters)

http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument

?key1=value1&key2=value2是請求裏提供的額外參數.這些參數是以鍵值對的形式,經過&符號分隔開來,服務器能夠經過這些參數進行相應的個性化處理

1.2.八、片斷(ref、fragment、hash、Anchor)

http://zhangguo:123456@www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument

#SomewhereInTheDocument是對資源的部分補充.fragment能夠理解爲資源內部的書籤.用來想服務器指明展現的內容所在的書籤的點.例如對於HTML文件來講,瀏覽器會滾動到特定的或者上次瀏覽過的位置.對於音頻或者視頻資源來講,瀏覽器又會跳轉到對應的時間節點。

錨記鏈接又叫命名錨記,命名錨記像一個迅速定位器同樣是一種頁面內的超級連接。

1.2.九、相對路徑和絕對路徑

咱們上面所說的都是絕對路徑,可是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>

結果:

1.三、hash

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 的錨部分(從 # 號開始的部分)

1.3.一、#的涵義

#表明網頁中的一個位置。其右面的字符,就是該位置的標識符。好比,

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>

結果:

1.3.二、HTTP請求不包括#

#是用來指導瀏覽器動做的,對服務器端徹底無用。因此,HTTP請求中不包括#。

好比,訪問下面的網址,

http://www.example.com/index.html#print

瀏覽器實際發出的請求是這樣的:

  GET /index.html HTTP/1.1

  Host: www.example.com

能夠看到,只是請求index.html,根本沒有"#print"的部分。

1.3.三、#後的字符

在第一個#後面出現的任何字符,都會被瀏覽器解讀爲位置標識符。這意味着,這些字符都不會被髮送到服務器端。

好比,下面URL的原意是指定一個顏色值:

http://www.example.com/?color=#fffccc

可是,瀏覽器實際發出的請求是:

  GET /?color= HTTP/1.1
  Host: www.example.com

能夠看到,"#fffccc"被省略了。只有將#轉碼爲%23,瀏覽器纔會將其做爲實義字符處理。也就是說,上面的網址應該被寫成:

http://example.com/?color=%23fffccc

1.3.四、改變#不觸發網頁重載

單單改變#後的部分,瀏覽器只會滾動到相應位置,不會從新加載網頁。

好比,從

http://www.example.com/index.html#location1

改爲

http://www.example.com/index.html#location2

瀏覽器不會從新向服務器請求index.html。

1.3.五、改變#會改變瀏覽器的訪問歷史

每一次改變#後的部分,都會在瀏覽器的訪問歷史中增長一個記錄,使用"後退"按鈕,就能夠回到上一個位置。

這對於ajax應用程序特別有用,能夠用不一樣的#值,表示不一樣的訪問狀態,而後向用戶給出能夠訪問某個狀態的連接。

值得注意的是,上述規則對IE 6和IE 7不成立,它們不會由於#的改變而增長曆史記錄。

1.3.六、window.location.hash讀取#值

window.location.hash這個屬性可讀可寫。讀取時,能夠用來判斷網頁狀態是否改變;寫入時,則會在不重載網頁的前提下,創造一條訪問歷史記錄。

1.3.七、SEO抓取#的機制

默認狀況下,Google的網絡蜘蛛忽視URL的#部分。

可是,Google還規定,若是你但願Ajax生成的內容被瀏覽引擎讀取,那麼URL中可使用"#!",Google會自動將其後面的內容轉成查詢字符串_escaped_fragment_的值。

好比,Google發現新版twitter的URL以下:

http://twitter.com/#!/username

就會自動抓取另外一個URL:

http://twitter.com/?_escaped_fragment_=/username

經過這種機制,Google就能夠索引動態的Ajax內容。

1.四、hashchange事件

這是一個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>

1.五、Hjax實現與緩存

結合前面的內容,咱們能夠模擬一個簡單的單頁示例,爲了提升性能這裏作了緩存,示例:

<!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>

結果:

 

2、Pjax(PushState + Ajax)

雖然傳統的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 刷新(實際效果以下圖的請求內容對比)。

2.0、History對象詳解(pushState、replaceState、popstate)

在沒有history ap以前,咱們常用散列值來改變頁面內容,特別是那些對頁面特別重要的內容。由於沒有刷新,因此對於單頁面應用,改變其URL是不可能的。此外,當你改變URL的散列值,它對瀏覽器的歷史記錄沒有任何影響。經過增長location.hash,並用onhashchange來達到目的。

如今對於HTML 5的History API來講,這些都是能夠輕易實現的,可是因爲單頁面應用不必使用散列值,它可能須要額外的開發腳本。它也容許咱們用一種對SEO友好的方式創建新應用。

2.0.一、length屬性

history.length屬性保存着歷史記錄的URL數量。初始時,該值爲1。因爲IE10+瀏覽器在初始時返回2,存在兼容性問題,因此該值並不經常使用。

2.0.二、跳轉方法go()、back()和forward()

history.go(n),進前或後退n步

history.back(),後退

 

history.forward(),前進

若是移動的位置超出了訪問歷史的邊界,以上三個方法並不報錯,而是靜默失敗

使用歷史記錄時,頁面一般從瀏覽器緩存之中加載,而不是從新要求服務器發送新的網頁,不觸發onload事件。

2.0.三、pushState()

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上有區別

若是設置了一個跨域網址,則會報錯。這樣設計的目的是,防止惡意代碼讓用戶覺得他們是在另外一個網站上

2.0.四、replaceState()

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

示例:

2.0.五、state

history.state屬性返回當前頁面的state對象

history.pushState({page: 1}, 'title 1', '?page=1');
history.state// { page: 1 }

2.0.六、popstate事件

每當同一個文檔的瀏覽歷史(即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>

結果:

2.0.七、往返緩存

默認狀況下,瀏覽器會在當前會話(session)緩存頁面,當用戶點擊「前進」或「後退」按鈕時,瀏覽器就會從緩存中加載頁面

瀏覽器有一個特性叫「往返緩存」(back-forward cache或bfcache),能夠在用戶使用瀏覽器的「後退」和「前進」按鈕時加快頁面的轉換速度。這個緩存中不只保存着頁面數據,還保存了DOM和javascript的狀態;其實是將整個頁面都保存在了內存裏。若是頁面位於bfcache中,那麼再次打開該頁面時就不會觸發load事件,IE10-瀏覽器不支持

2.0.八、pageshow

pageshow事件在頁面加載時觸發,包括第一次加載和從緩存加載兩種狀況。若是要指定頁面每次加載(無論是否是從瀏覽器緩存)時都運行的代碼,能夠放在這個事件的監聽函數

第一次加載時,它的觸發順序排在load事件後面。從緩存加載時,load事件不會觸發,由於網頁在緩存中的樣子一般是load事件的監聽函數運行後的樣子,因此沒必要重複執行。同理,若是是從緩存中加載頁面,網頁內初始化的JavaScript腳本(好比DOMContentLoaded事件的監聽函數)也不會執行

[注意]雖然這個事件的目標是document,但必須將其事件處理程序添加到window

pageshow事件有一個persisted屬性,返回一個布爾值。頁面第一次加載時或沒有從緩存加載時,這個屬性是false;當頁面從緩存加載時,這個屬性是true

[注意]上面的例子使用了私有做用域,以防止變量showCount進入全局做用域。若是單擊了瀏覽器的「刷新」按鈕,那麼showCount的值就會被重置爲0,由於頁面已經徹底從新加載了

2.0.九、pagehide

與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

2.一、原理

利用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,它包含的信息指出文件的位置以及瀏覽器應該怎麼處理它。

2.二、jQuery-Pjax

2.2.一、pjax概要

pjax是jquery的一個插件,它使用ajax和pushState兩個技術改善用戶的網頁瀏覽體驗。具體來講,當用戶使用a標籤切換頁面時,能夠實現局部刷新的技術。

github源碼:https://github.com/defunkt/jquery-pjax

官網:https://pjax.herokuapp.com

pjax主要作兩方面的事兒:

  1. 用戶點擊連接發送ajax請求,服務器獲得請求返回須要填充的HTML片斷,客戶端獲得HTML片斷而後插入更新區域
  2. 頁面填充完畢後,使用pushState更新當前的URL

這個過程能實現頁面局部刷新,比傳統的頁面切換刷新的體驗好一些,由於:

  1. 只下載須要的HTML頁面片斷,沒有JS、CSS解析
  2. 若是服務端配置了正確的pjax請求,則只返回要更新的HTML片斷,客戶端只更新必要的內容,避免了頁面從新渲染的過程。

優勢:

減輕服務端壓力

按需請求,每次只需加載頁面的部份內容,而不用重複加載一些公共的資源文件和不變的頁面結構,大大減少了數據請求量,以減輕對服務器的帶寬和性能壓力,還大大提高了頁面的加載速度。

優化頁面跳轉體驗

常規頁面跳轉須要從新加載畫面上的內容,會有明顯的閃爍,並且每每和跳轉前的頁面沒有連貫性,用戶體驗不是很好。若是再趕上頁面比較龐大、網速又不是很好的狀況,用戶體驗就更加雪上加霜了。使用pjax後,因爲只刷新部分頁面,切換效果更加流暢,並且能夠定製過分動畫,在等待頁面加載的時候體驗就比較舒服了。

缺點:

不支持一些低版本的瀏覽器(如IE系列)

pjax使用了pushState來改變地址欄的url,這是html5中history的新特性,在某些舊版瀏覽器中可能不支持。不過pjax會進行判斷,功能不適用的時候會執行默認的頁面跳轉操做。

使服務端處理變得複雜

要作到普通請求返回完整頁面,而pjax請求只返回部分頁面,服務端就須要作一些特殊處理,固然這對於設計良好的後端框架來講,添加一些統一處理仍是比較容易的,天然也沒太大問題。另外,即便後臺不作處理,設置pjax的fragment參數來達到一樣的效果。

綜合來看,pajx的優勢很強勢,缺點也幾乎能夠忽略,仍是很是值得推薦的,尤爲是相似博客這種大部分狀況下只有主體內容變化的網站。關鍵它使用簡單、學習成本小,即時全站只有極個別頁面能用獲得,嘗試下沒什麼損失。pjax的github主頁介紹的已經很詳細了,想了解更多能夠看下源碼。

2.2.二、使用方法

1. 客戶端

客戶端設置分兩步:

  1. 下載插件,包括jquery1.8+,或者npm安裝。https://github.com/defunkt/jquery-pjax
  2. 初始化pjax插件,並有條件的攔截a標籤跳轉。
初始化
$.fn.pjax

下面代碼表示:當selector被點擊時,執行ajax請求,並將返回的HTML字符串填充在container標記的位置。

$(document).pjax(selector, [container], options)

參數說明

  • selector:click事件的選擇器
  • container:pjax容器id
  • 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

事件

1. 點擊連接後觸發的一系列事件, 除了 pjax:clickpjax: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 腳本不會被重複加載,有必要能夠改下源碼
2. 瀏覽器前進/後退導航時觸發的事件
事件名 參數 說明
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');

2. 服務端

服務端也比較簡單,監聽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>

運行結果:

2.2.三、API介紹

這部分用於更細粒度的控制。

$.pjax.click

示例:

// 肯定能使用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.submit

用pjax提交表單

$(document).on('submit', 'form[data-pjax]', function(event) {
  $.pjax.submit(event, '#pjax-container')
})

$.pjax.reload

對當前URL使用pjax的方式從新獲取HTML代碼片斷,而且在指定容器替換,這個過程不添加新的歷史記錄。(子片斷重刷新)

$.pjax.reload('#pjax-container', options)

$.pjax

不是經過click觸發pjax的時候使用。好比某些操做後自動觸發pjax的過程。若是能獲取到clickevent事件時,建議使用$.pjax-click(event)替換。

function applyFilters() {
  var url = urlForFilters()
  $.pjax({url: url, container: '#pjax-container'})
}

2.2.四、pjax生命週期

pjax生命週期簡單的說:

 
點擊pjax連接
 
觸發瀏覽器前進後退

生命週期和Loading組件使用密切:

$(document).on('pjax:send', function() {
  $('#loading').show()
})
$(document).on('pjax:complete', function() {
  $('#loading').hide()
})

2.2.五、高級技巧

子頁面加載完畢初始化其中的插件/組件

pjax只是請求HTML片斷以後插入指定位置,所以片斷內的JS插件/組件初始化須要在pjax:end事件後執行。

$(document).on('ready pjax:end', function(event) {
  $(event.target).initializeMyPlugin()
})

這段代碼會在document ready或者container ready後執行initializeMyPlugin初始化方法(包括前進後退)。

強制reload

當使用pjax致使整個頁面被強制刷時,可能的緣由是:

  • 當返回的HTML片斷包含<html>標籤且fragment選擇器沒有指定時。若是指定了fragment選擇器,pjax將從HTML文檔中提取須要局部刷新的子片斷。
  • 服務端返回的內容爲空時。
  • HTTP響應的code是 4xx 或者 5xx。

瀏覽器重定向

在響應頭中設置X-PJAX-URL,例如:

request.headers['X-PJAX-URL'] = "http://example.com/hello"

Layout從新加載

當客戶端頁面的pjax版本和服務器返回的pjax版本不一致時,頁面會從新刷新。

客戶端頁面的pjax版本:

<meta http-equiv="x-pjax-version" content="v123">

若是服務器修改了版本則從新刷新:

response.headers['X-PJAX-Version'] = "xxxx修改版本名稱xxxx"

2.2.五、使用建議

這貨須要服務端密切配合,若是服務端沒設置好,要不就是請求只返回HTML片斷,要不每次頁面切換都是從新加載頁面。

若是服務端沒法完成這些配置,只能ajax異步由前端本身拼接HTML來作,建議使用MV*的庫來作這部分。

插件伴侶——NProgress

官網:http://ricostacruz.com/nprogress/

github:https://github.com/rstacruz/nprogress/

比較漂亮的一款進度條插件,用法十分簡單,很適合作pjax的過分動畫,詳細用法在該項目github上有介紹

3、Vue Router

3.一、概要

Vue Router是一個Vue核心插件,是Vue.js官方的路由管理器。它和 Vue.js 的核心深度集成,讓構建單頁面應用變得易如反掌。vue的單頁面應用是基於路由和組件的,路由用於設定訪問路徑,並將路徑和組件映射起來。傳統的頁面應用,是用一些超連接來實現頁面切換和跳轉的。在vue router單頁面應用中,則是路徑之間的切換,也就是組件的切換。包含的功能有:

  • 嵌套的路由/視圖表
  • 模塊化的、基於組件的路由配置
  • 路由參數、查詢、通配符
  • 基於 Vue.js 過渡系統的視圖過渡效果
  • 細粒度的導航控制
  • 帶有自動激活的 CSS class 的連接
  • HTML5 歷史模式或 hash 模式,在 IE9 中自動降級
  • 自定義的滾動條行爲

3.1.一、資源

中文幫助:https://router.vuejs.org/zh/

英文幫助:https://router.vuejs.org/

Git源碼:https://github.com/vuejs/vue-router

3.1.二、概念

路由中有三個基本的概念 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。

3.1.三、底層實現

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 服務器端。

3.二、安裝

3.2.一、直接下載 / CDN

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>

3.2.二、NPM

使用nodejs包管理器安裝

npm install vue-router

若是在一個模塊化工程中使用它,必需要經過 Vue.use() 明確地安裝路由功能:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

若是使用全局的 script 標籤,則無須如此 (手動安裝)。

3.2.三、構建開發版

若是你想使用最新的開發版,就得從 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

3.三、第一個路由示例

3.3.一、網頁版

用 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>

運行結果:

3.3.二、Vue-cli版

安裝:

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>

目錄結構以下:

運行結果: 

 

單頁切換

3.四、路由模式

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

3.五、重定向

重定向經過 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' /*命名*/
    }
  ]
})

結果:

3.六、別名

重定向是指,當用戶訪問 /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' /*命名,重定向*/
    }
  ]
})
View Code

連接:

<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>
View Code

結果:

3.七、根路徑

設置根路徑,須要將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 },
]

運行結果:

3.八、嵌套路由

實際項目中的應用界面,一般由多層嵌套的組件組合而成。一樣地,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>
View Code

Foo2.Vue

<template>
  <div>
    <h2>這是Foo2</h2>
  </div>
</template>

<script>
  export default {
    name: "Foo2"
  }
</script>

<style scoped>
  h2 {
    color: orange;
  }
</style>
View Code

Foo3.Vue

<template>
  <div>
    <h2>這是Foo3</h2>
  </div>
</template>

<script>
  export default {
    name: "Foo3"
  }
</script>

<style scoped>
  h2 {
    color:springgreen;
  }
</style>
View Code

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>
View Code

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

3.九、命名路由

有時,經過一個名稱來標識一個路由顯得更方便,特別是在連接一個路由,或者是執行一些跳轉時。能夠在建立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>

結果以下所示

3.十、命名視圖

有時候想同時(同級)展現多個視圖,而不是嵌套展現,例如建立一個佈局,有 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
   } },
]
 

結果以下所示

 

3.十一、動態路徑

常常須要把某種模式匹配到的全部路由,全都映射到同個組件。例如,有一個 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},
    ],
  }), 
})
 

 

3.十二、查詢字符串

實現子路由,除了使用動態參數,也可使用查詢字符串

 
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();
    },
  }
};
 

 

3.1三、滾動行爲

使用前端路由,當切換到新路由時,想要頁面滾到頂部,或者是保持原先的滾動位置,就像從新加載頁面那樣。 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}
    }
  }
})
 

 

3.1四、過渡動效

<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>  ` }
 

 

3.1五、路由元信息

定義路由的時候能夠配置 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';

    }
  },  
})
 

 

3.1六、編程式導航

除了使用<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.pushrouter.replacerouter.gohistory.pushState、history.replaceStatehistory.go相似, 實際上它們確實是效仿window.historyAPI的。vue-router的導航方法(pushreplacego)在各種路由模式(historyhashabstract)下表現一致

 

3.1七、導航鉤子

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) 來取消導航

 

3.1八、數據獲取

有時候,進入某個路由後,須要從服務器獲取數據。例如,在渲染用戶信息時,須要從服務器獲取用戶的數據。能夠經過兩種方式來實現:

  一、導航完成以後獲取:先完成導航,而後在接下來的組件生命週期鉤子中獲取數據。在數據獲取期間顯示『加載中』之類的指示

  二、導航完成以前獲取:導航完成前,在路由的 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
        }
      })
    }
  }
}
 

在爲後面的視圖獲取數據時,用戶會停留在當前的界面,所以建議在數據獲取期間,顯示一些進度條或者別的指示。若是數據獲取失敗,一樣有必要展現一些全局的錯誤提醒

 

3.1九、懶加載

當打包構建應用時,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 會將任何一個異步模塊與相同的塊名稱組合到相同的異步塊中

相關文章
相關標籤/搜索