【vue回顾系列】20-大白话讲解vue-router路由实现的基本原理,保证让你绝对能懂!

前言

文章相较于之前做了删减,将稍微深入一点的知识点(例如源码,拓展)和一些表述比较乱的地方剔除,目的是为了能够用最小精力去记住,并且输出。

本文章保证能让你懂,并且教你如何输出!


什么是单页面

在过去的前后端未分离时代,页面的每次跳转都是请求对应的html文件到浏览器进行渲染,页面才能展示。

而现在前后端分离时代,客户端一次性加载大部分的前端文件,url变化时,不用向后台请求新的html文件,只需要将当前页面内的html内容进行更换,渲染,就可以实现页面内容的切换。这就是单页面,简称SPA。

Vue-router的主要作用就是实现如何根据url的变化去更新视图,实现单页面效果。

优点

  • 相较过去,页面内容切换的时候很快,用户体验更好。
  • 服务器压力没以前大了,毕竟很多工作都放在了浏览器上做。

缺点

  • 第一次加载页面文件会比较多,如果是大型项目,可能会出现首页加载缓慢问题,需要手动优化。
  • SEO优势削弱,没有像不分离时代或者SSR服务端渲染那样的优势。

路由模式

在浏览器环境中,由router配置里的mode属性设置路由模式而决定实现原理;

主要有三种模式:

  1. Hash模式(默认,如果浏览器不支持history模式,也会采用hash模式)
  2. Hisrtory模式(实现建立在h5新增的api,所以也叫H5 Hisrtory)
  3. Abstract模式(在node环境下,此次不涉及)

我们平时在参数 mode 上切换路由方式,其实是在创建VueRouter类里面对应的实例对象(这个不懂没关系,继续看):

switch (mode) {
  case 'history':
    this.history = new HTML5History(this, options.base)
    break
  case 'hash':
    this.history = new HashHistory(this, options.base, this.fallback)
    break
  case 'abstract':
    this.history = new AbstractHistory(this, options.base)
    break
  default:
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `invalid mode: ${mode}`)
    }
}

Hash模式

hash符号#的本来作用是加在URL中指示网页中的位置:

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

#符号本身以及它后面的字符称之为hash,可通过 window.location.hash 属性读取。

特点或原理

第一:

hash虽然出现在URL中,但不会被包括在HTTP请求中。例如当地址后面加上了hash值为:https://xxx.com/home#111时,是不会向服务端发送资源获取请求的。因此,改变hash不会去请求页面资源

第二:

当哈希值改变了就会触发内置事件hashchange,触发条件有:

  1. 直接更改浏览器地址,在最后面增加或改变#hash;
  2. 通过改变location.hreflocation.hash的值;
  3. 通过触发点击带锚点的链接;
  4. 浏览器前进后退可能导致hash的变化,前提是两个网页地址的hash值不同;

第三:

每一次改变hash(window.location.hash),都会在浏览器的访问历史中增加一个记录

利用hash的以上特点,就可以来实现前端路由更新视图但不重新请求页面的功能了。

简单实现

写个简单的实现例子:

<body>
    <a class="a-class" href="#home">点我切换</a>
    <div id="view">原视图</div>
    <script>
        // router-view组件
        window.addEventListener("hashchange", (e) => {
            console.log('hashchange', e);
            // 这里就可以做视图的更新了
            let viewDom = view // 想不到吧!还可以这样获取dom
            viewDom.innerHTML = '视图切换' // 做视图的切换
        });

        // 页面初次加载,获取hash
        document.addEventListener("DOMContentLoaded", () => {
            console.log("最初始的hash:", location.hash);

            // router-link组件
            document.querySelectorAll(".a-class").forEach((item) => {
                // 给每个a标签的点击事件加上监听
                item.addEventListener(
                    "click",
                    (e) => {
                        // 阻止a标签的默认跳转事件
                        e.preventDefault();
                        // 获取a标签的链接内容
                        let link = item.getAttribute('href');
                        // 添加到锚点后,每次点击后url都能更新哈希值
                        location.hash = link;
                    },
                    false
                );
            });
        });
    </script>
</body>

所以流程大致为:

  • 监听a标签的点击事件
  • 阻止a标签的默认行为,并用location.hash更改哈希值
  • hashchange监听事件中切换视图

优缺点

优点:

  • 兼容性好,下到ie8
  • 服务端不用配置啥东西

缺点:

  • 地址有个#号,看起来不美观

History模式

解释

History interface是浏览器历史记录栈提供的接口,在过去时期,通过back()forward()go(),打开本标签新地址等方法,我们可以改变浏览器历史记录栈的信息,进行各种地址跳转操作(其实就是控制浏览器的前进后退键),浏览器就会按照地址向服务端发送资源请求,服务器会返回对应的页面html文件,浏览器拿到后再进行渲染。

pushState与replaceState

从HTML5开始,History interface有了新增:pushState()replaceState()

window.history.pushState(stateObject, title, URL) // 往后新增一个路由记录
window.history.replaceState(stateObject, title, URL) // 把当前的路由记录替换
  • stateObject: 一个对象类型的变量,可以带入一些信息;
  • title: 所添加记录的标题;
  • URL: 所添加记录的URL;

这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前URL改变了,但浏览器并没有前进后退,所以不会立即发送请求该URL,这就为单页应用前端路由更新视图但不重新请求页面提供了基础。

可以自己随便找个网址,然后控制台试试下面代码,你就会发现地址改变了,但页面什么都没有发生。

history.replaceState({}, null, '/b') // 替换路由
history.pushState({}, null, '/a') // 路由压栈

接下来我们只需要一个api,监听地址发生了改变,就去更新视图。这就是PopState事件。

PopState

replaceState与pushState的调用并不会触发PopState,只有浏览器的前进后退键,back、forward、go等方法才会触发这个地址监听。

那咋整?咱可以直接手动调用。

触发了PopState后,也就能获取到replaceState与pushState他俩传过来的参数

window.addEventListener("popstate", (e) => {
  // 这里就可以做视图的更新了 render()
  console.log("onpopstate", e.state);
});

简单实现

<body>
    <a class="a-class" href="/home">点我切换</a>
    <div id="view">原视图</div>
    <script>
        // router-view组件
        window.addEventListener("popstate", (e) => {
            // 这里就可以做视图的更新了
            console.log("popstate", e.state, location.pathname);
            let viewDom = view // 想不到吧!还可以这样获取dom
            viewDom.innerHTML = e.state.content // 做视图的切换
        });

        // 页面初次加载,获取 path
        document.addEventListener("DOMContentLoaded", () => {
            console.log("load", location.pathname);

            // router-link组件
            document.querySelectorAll(".a-class").forEach((item) => {
                // 给每个a标签的点击事件加上监听
                item.addEventListener(
                    "click",
                    (e) => {
                        e.preventDefault(); // 阻止a标签的默认跳转事件
                        let link = item.getAttribute('href'); // 获取a标签的链接内容
                        let data = { content: '视图切换' }
                        // 先检查是否有history对象,并且是否支持h5的新api
                        if (!!window.history && history.pushState) {
                            // 因为pushState的第一个参数可以传对象,所以能传的数据比hash模式多
                            window.history.pushState(data, link, link);
                            // 手动触发popstate
                            let popStateEvent = new PopStateEvent('popstate', { state: data });
                            dispatchEvent(popStateEvent);
                        } else {
                            // 安装polyfill补丁,不能就回到hash模式
                        }
                    },
                    false
                );
            });
        });
    </script>
</body>

所以流程大致为:

  • 监听a标签的点击事件
  • 阻止a标签的默认行为,并调用pushState改变路由地址,接着new PopStateEvent('popstate'),再用dispatchEvent手动触发popstate事件
  • popstate事件监听中切换视图

服务端配置

前端的都搞定了,还需要后端配置一下。因为浏览器每次改变地址后最终我们都是要触发资源请求的,所以需要让服务器知道,无论我们请求什么地址,都只给我们返回index.html的资源,这样浏览器收到后发现与现在访问的资源一样,就不会重新加载了。

在nginx中配置

server {
  listen  80;
  server_name  www.xxx.com;

  location / {
    index  /data/dist/index.html;
    try_files $uri $uri/ /index.html; // 这个就是关键,只返回index.html
  }
}

然后前端最后在自己的代码判断,如果地址不在路由表的范围内,跳去404页面。

优缺点

优点:

  • 美观

缺点:

  • 需要服务端支持
  • 兼容性没到ie8
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值