• 前端MVC Vue2学习总结(八)——前端路由


    路由是根据不同的 url 地址展示不同的内容或页面,早期的路由都是后端直接根据 url 来 reload 页面实现的,即后端控制路由。

    后来页面越来越复杂,服务器压力越来越大,随着AJAX(异步刷新技术) 的出现,页面实现非 reload 就能刷新数据,让前端也可以控制 url 自行管理,前端路由因此而生。

    如果直接使用AJAX加载页面片段是可以实现单页效果的,但这样会破坏浏览器的前进与后退功能,使用Hjax或Pjax技术后即可以实现单页无刷新效果又不会影响前进与后退功能。

    示例:

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

    <div style="background: antiquewhite">
        <h2>这是页面一</h2>
    </div>

    page2.html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>这是页面二</title>
    </head>
    <body>
    <div style="background: dodgerblue">
        <h2>这是页面二</h2>
    </div>
    </body>
    </html>

    运行结果:

    单页面应用的实现,就是因为前端路由,前端路由实现主要有下面几种方法:

    一、Hjax(Hash + Ajax)

    1.1、原理

    原理:url 中常会出现 #,一可以表示锚点(如回到顶部按钮的原理),二是路由里的锚点(hash)。Web 服务并不会解析 hash,也就是说 # 后的内容 Web 服务都会自动忽略,但是 JavaScript 是可以通过 window.location.hash 读取到的,读取到路径加以解析之后就可以响应不同路径的逻辑处理。

    hashchange 事件(监听 hash 变化触发的事件),当用 window.location 处理哈希的改变时不会重新渲染页面,而是当作新页面加到历史记录中,这样我们跳转页面就可以在 hashchange 事件中注册 ajax 从而改变页面内容。

    <!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.2、URL详解

    URL是统一资源定位符,对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。URI是统一资源标识符,而URL是统一资源定位符,我们把URL理解为是URI的一个子类,而另一种子类是URN。url是统一资源定位符,对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。URL中所有的字符都是ASCII字符集,如果出现非ASCII字符集,比如中文,浏览器会先进行编码再进行传输。

    1.2.1、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.2、用户名与密码

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

    这里用户名是zhangguo,密码是123456,如果带@符用户必须填写,密码选填,带用户名与密码的情况很少见,很多时候都放到了参数中了。

    1.2.3、协议(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.4、域名(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.5、端口(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.6、文件路径

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

    /path/to/myfile.html表示服务器上资源的路径,过去这样的路径标记的是服务器上文件的物理路径,但是现在,路径表示的只是一个抽象地址,并不指代任何物理地址.

    1.2.7、参数(query、Parameters)

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

    ?key1=value1&key2=value2是请求里提供的额外参数.这些参数是以键值对的形式,通过&符号分隔开来,服务器可以通过这些参数进行相应的个性化处理

    1.2.8、片段(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.9、相对路径和绝对路径

    我们上面所说的都是绝对路径,但是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.3、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.1、#的涵义

    #代表网页中的一个位置。其右面的字符,就是该位置的标识符。比如,

    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.2、HTTP请求不包括#

    #是用来指导浏览器动作的,对服务器端完全无用。所以,HTTP请求中不包括#。

    比如,访问下面的网址,

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

    浏览器实际发出的请求是这样的:

      GET /index.html HTTP/1.1
    
      Host: www.example.com

    可以看到,只是请求index.html,根本没有"#print"的部分。

    1.3.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.4、改变#不触发网页重载

    单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页。

    比如,从

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

    改成

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

    浏览器不会重新向服务器请求index.html。

    1.3.5、改变#会改变浏览器的访问历史

    每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用"后退"按钮,就可以回到上一个位置。

    这对于ajax应用程序特别有用,可以用不同的#值,表示不同的访问状态,然后向用户给出可以访问某个状态的链接。

    值得注意的是,上述规则对IE 6和IE 7不成立,它们不会因为#的改变而增加历史记录。

    1.3.6、window.location.hash读取#值

    window.location.hash这个属性可读可写。读取时,可以用来判断网页状态是否改变;写入时,则会在不重载网页的前提下,创造一条访问历史记录。

    1.3.7、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.4、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.5、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>

    结果:

    二、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.1、length属性

    history.length属性保存着历史记录的URL数量。初始时,该值为1。由于IE10+浏览器在初始时返回2,存在兼容性问题,所以该值并不常用。

    2.0.2、跳转方法go()、back()和forward()

    history.go(n),进前或后退n步

    history.back(),后退

     

    history.forward(),前进

    如果移动的位置超出了访问历史的边界,以上三个方法并不报错,而是静默失败

    使用历史记录时,页面通常从浏览器缓存之中加载,而不是重新要求服务器发送新的网页,不触发onload事件。

    2.0.3、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.4、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.5、state

    history.state属性返回当前页面的state对象

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

    2.0.6、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.7、往返缓存

    默认情况下,浏览器会在当前会话(session)缓存页面,当用户点击“前进”或“后退”按钮时,浏览器就会从缓存中加载页面

    浏览器有一个特性叫“往返缓存”(back-forward cache或bfcache),可以在用户使用浏览器的“后退”和“前进”按钮时加快页面的转换速度。这个缓存中不仅保存着页面数据,还保存了DOM和javascript的状态;实际上是将整个页面都保存在了内存里。如果页面位于bfcache中,那么再次打开该页面时就不会触发load事件,IE10-浏览器不支持

    2.0.8、pageshow

    pageshow事件在页面加载时触发,包括第一次加载和从缓存加载两种情况。如果要指定页面每次加载(不管是不是从浏览器缓存)时都运行的代码,可以放在这个事件的监听函数

    第一次加载时,它的触发顺序排在load事件后面。从缓存加载时,load事件不会触发,因为网页在缓存中的样子通常是load事件的监听函数运行后的样子,所以不必重复执行。同理,如果是从缓存中加载页面,网页内初始化的JavaScript脚本(比如DOMContentLoaded事件的监听函数)也不会执行

    [注意]虽然这个事件的目标是document,但必须将其事件处理程序添加到window

    pageshow事件有一个persisted属性,返回一个布尔值。页面第一次加载时或没有从缓存加载时,这个属性是false;当页面从缓存加载时,这个属性是true

    [注意]上面的例子使用了私有作用域,以防止变量showCount进入全局作用域。如果单击了浏览器的“刷新”按钮,那么showCount的值就会被重置为0,因为页面已经完全重新加载了

    2.0.9、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);
    }

    使用方法:

    1、取消默认的返回操作

    function pushHistory(){
        var state = {
        title: "title",
        url: "#" 
    }
    window.history.pushState(state, "title", "#"); 
    }
    
    pushHistory()

    2、history.js用于兼容html4,也可以监听pushState与replaceSatea

    history在某些浏览器上支持还不是特别好,可以引用这个实现兼容的js

    https://github.com/browserstate/history.js

    2.1、原理

    利用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.2、jQuery-Pjax

    2.2.1、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.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参数配置

    keydefaultdescription
    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.3、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.4、pjax生命周期

    pjax生命周期简单的说:

     
    点击pjax链接
     
    触发浏览器前进后退

    生命周期和Loading组件使用密切:

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

    2.2.5、高级技巧

    子页面加载完毕初始化其中的插件/组件

    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.5、使用建议

    这货需要服务端密切配合,如果服务端没设置好,要不就是请求只返回HTML片段,要不每次页面切换都是重新加载页面。

    如果服务端无法完成这些配置,只能ajax异步由前端自己拼接HTML来做,建议使用MV*的库来做这部分。

    插件伴侣——NProgress

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

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

    比较漂亮的一款进度条插件,用法十分简单,很适合做pjax的过度动画,详细用法在该项目github上有介绍

    三、Vue Router

    3.1、概要

    Vue Router是一个Vue核心插件,是Vue.js官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue router单页面应用中,则是路径之间的切换,也就是组件的切换。包含的功能有:

    • 嵌套的路由/视图表
    • 模块化的、基于组件的路由配置
    • 路由参数、查询、通配符
    • 基于 Vue.js 过渡系统的视图过渡效果
    • 细粒度的导航控制
    • 带有自动激活的 CSS class 的链接
    • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
    • 自定义的滚动条行为

    3.1.1、资源

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

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

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

    3.1.2、概念

    路由中有三个基本的概念 route, routes, router。

    1、 route,它是一条路由,由这个英文单词也可以看出来,它是单数, Home按钮 => home内容, 这是一条route, about按钮 => about 内容, 这是另一条路由。

    2、 routes 是一组路由,把上面的每一条路由组合起来,形成一个数组。[{home 按钮 =>home内容 }, { about按钮 => about 内容}]

    3、router 是一个机制,相当于一个管理者,它来管理路由。因为routes 只是定义了一组路由,它放在哪里是静止的,当真正来了请求,怎么办? 就是当用户点击home 按钮的时候,怎么办?这时router 就起作用了,它到routes 中去查找,去找到对应的 home 内容,所以页面中就显示了 home 内容。

    4、客户端中的路由,实际上就是dom 元素的显示和隐藏。当页面中显示home 内容的时候,about 中的内容全部隐藏,反之也是一样。客户端路由有两种实现方式:基于hash 和基于html5 history api。

    3.1.3、底层实现

    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.2、安装

    3.2.1、直接下载 / 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.2、NPM

    使用nodejs包管理器安装

    npm install vue-router

    如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能:

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)

    如果使用全局的 script 标签,则无须如此 (手动安装)。

    3.2.3、构建开发版

    如果你想使用最新的开发版,就得从 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.3.1、网页版

    用 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.2、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.4、路由模式

    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.5、重定向

    重定向通过 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.6、别名

    重定向是指,当用户访问 /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.7、根路径

    设置根路径,需要将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.8、嵌套路由

    实际项目中的应用界面,通常由多层嵌套的组件组合而成。同样地,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.9、命名路由

    有时,通过一个名称来标识一个路由显得更方便,特别是在链接一个路由,或者是执行一些跳转时。可以在创建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.10、命名视图

    有时候想同时(同级)展示多个视图,而不是嵌套展示,例如创建一个布局,有 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.11、动态路径

    经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,有一个 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.12、查询字符串

    实现子路由,除了使用动态参数,也可以使用查询字符串

     
    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.13、滚动行为

    使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 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.14、过渡动效

    <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.15、路由元信息

    定义路由的时候可以配置 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.16、编程式导航

    除了使用<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.17、导航钩子

    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.18、数据获取

    有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,需要从服务器获取用户的数据。可以通过两种方式来实现:

      1、导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示『加载中』之类的指示

      2、导航完成之前获取:导航完成前,在路由的 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.19、懒加载

    当打包构建应用时,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 会将任何一个异步模块与相同的块名称组合到相同的异步块中

  • 相关阅读:
    125-PHP类__set()魔术方法
    124-PHP类析构函数
    123-PHP类构造函数
    122-PHP类成员函数(三)
    121-PHP类成员函数(二)
    120-PHP调用成员方法并将不同类的对象做为参数
    119-PHP调用private成员的方法
    118-PHP调用带参数的成员方法
    117-PHP在外部无法调用private类成员函数
    HDU-2045 不容易系列之(3)—— LELE的RPG难题 找规律&递推
  • 原文地址:https://www.cnblogs.com/best/p/10219576.html
Copyright © 2020-2023  润新知