后端说,单页面SPA和前端路由是怎么回事

news/2024/9/18 18:54:22/文章来源:https://www.cnblogs.com/ggtc/p/18322846

没有请求的路由

在传统开发中,浏览器点击一个超链接,就会像后端web服务器发送一个html文档请求,然后页面刷新。但开始单页面开发后,就完全不同了。

单页面?这个概念难以理解。我用一个js作为整个web应用,然后再这个js中操作dom变化,以此来实现页面变化。这不叫单页面吗?这叫!但不完善,因为这种方法破坏了浏览器自带的导航功能。比如前进,后退。所以单页面前端应用要解决两件事内容变化导航变化。这是现代前端成立的基础。

想必初次接触vue-routernuxt的人很多对前端路由困惑。明明浏览器地址栏的链接变了,为什么浏览器却没有发送请求出去?至少我是很疑惑的。

这要归功于一个浏览器API,History API。学过wpf的人可能对这玩意不陌生,因为wpf也可以借助导航开发浏览器式应用程序。

导航

导航这种理念包括如下几种功能

  • history.back()
    后退到上一个页面

  • history.forward()
    前进到下一个页面

  • history.go(-2)
    跳转到前两个页面

但下面这三个方法才是实现单页面的关键。因为调用这三个api设置location不会引起浏览器向服务器发送页面请求

  • history.pushState(data, title, url)
  • history.popState()
  • history.replaceState(data, title, url)

history导航是一个栈,这是用来操作栈的方法,入栈、出栈、更新栈顶元素。只不过这个栈里面存放的是页面相关信息。最重要的是pushState,其他的可以暂时不管,也不影响使用。

还有一个关键的,当点击导航浏览器前进和后退按钮,会触发事件window.onpopstate。在这里,我们用js读取导航栈中的信息,并操作dom。这解决了和浏览器导航集成的问题。

这些 API 的主要目的是支持像单页应用这样的网站,它们使用 JavaScript API(如 fetch())来更新页面的新内容,而不是加载整个新页面。

其实到这里,单页面基实现的本原理已经清楚了。

实现一个简单的单页面应用

容器

单页面应用需要一个容器,这里我使用一个div作为页面的容器。

<div id="app"></div>

实现页面

页面由模板js代码组成。为了方便书写,各自放在一个script标签中。在模板中声明页面结构,用type="text/html"属性,使得我们可以获得语法感知的提示。然后再定义脚本,其实就是一个函数。在其中读取模板,请求数据,然后渲染页面到容器中

<!-- 模板 --><script type="text/html" id="page1_html"><h1>首页</h1><h2>这是第一个页面</h2><div id="content"></div><button style="background-color: lightcoral;" onclick="route.routeTo('/page2')">跳转到关于页面</button></script>
<!-- 脚本 --><script id="page1_js">function loadPage1(){//把模板页面替换进容器document.getElementById("app").innerHTML=document.getElementById("page1_html").innerHTML;//取数据然后生成内容,实际可能有ajax和fetch请求var data={text:"这是使用js生成的内容",id:1};document.getElementById("content").innerText=JSON.stringify(data);}</script>

前端路由调度

光有模板和脚本不行,我们还需要一个调度算法,用来调用页面渲染函数、更新导航。就是所谓的前端路由功能了。为了便于观察,我把声明的页面放在一个对象中,这就形成了路由表,方便搜索。当然,不要这个也是可以的,可以手动调用脚本渲染函数loadPage1

const route={page:[{url:"/page1",module:loadPage1},{url:"/page2",module:loadPage2}]
}

有了路由表之后,就可以添加调度算法,根据传入的url,寻找到对应的对象,添加到导航栈中,然后调用脚本渲染函数loadPageXXX

const route={routeTo:(url)=>{var page=route.page.find(r=>r.url==url);if(page==null){alert("文件不存在");}else{if(window.location.pathname==url)return;history.pushState(page.url,"",page.url);page.module("这里可以传参数");}}
}
//默认跳转到首页
route.routeTo("/page1");

到了这一个,已经实现了单页面。点击跳转按钮,地址栏会变,页面也会变。但有一个问题,点击浏览器导航按钮不管用。这是因为我们还没监听popstate事件处理这个操作。

因为点击导航按钮,地址栏会变,但页面渲染什么内容?这需要我们去处理。所以在这个事件中,我们根据url,从路由表中找到页面,然后渲染出来。

// 处理前进与后退window.addEventListener("popstate",(e)=>{var page=route.page.find(r=>r.url==e.state);if (page) {page.module("这里可以传参数");}})

效果

可以看到,切换页面时,地址栏变了,但并没有网络请求发出

image

完整代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>SPA页面</title>
</head>
<body><div id="app"></div><!-- 首页 --><script type="text/html" id="page1_html"><h1>首页</h1><h2>这是第一个页面</h2><div id="content"></div><button style="background-color: lightcoral;" onclick="route.routeTo('/page2')">跳转到关于页面</button></script><script id="page1_js">function loadPage1(){//把模板页面替换进容器document.getElementById("app").innerHTML=document.getElementById("page1_html").innerHTML;//取数据然后生成内容,实际可能有ajax和fetch请求var data={text:"这是使用js生成的内容",id:1};document.getElementById("content").innerText=JSON.stringify(data);}</script><!-- 关于页 --><script type="text/html" id="page2_html"><h1>关于</h1><h2>这是第二个页面</h2><div id="content"></div><button style="background-color: lightgreen;" onclick="route.routeTo('/page1')">跳转到首页</button></script><script id="page2_js">function loadPage2(){//把模板页面替换进容器document.getElementById("app").innerHTML=document.getElementById("page2_html").innerHTML;//取数据然后生成内容,实际可能有ajax和fetch请求var data={text:"假设这里是网站信息",id:2};document.getElementById("content").innerText=JSON.stringify(data);}</script><!-- 调度 --><script>const route={page:[{url:"/page1",module:loadPage1},{url:"/page2",module:loadPage2}],routeTo:(url)=>{var page=route.page.find(r=>r.url==url);if(page==null){alert("文件不存在");}else{if(window.location.pathname==url)return;history.pushState(page.url,"",page.url);page.module("这里可以传参数");}}}// 处理前进与后退window.addEventListener("popstate",(e)=>{var page=route.page.find(r=>r.url==e.state);if (page) {page.module("这里可以传参数");}})//默认跳转到首页route.routeTo("/page1");</script>
</body>
</html>

结语

从上面的实现中,你们也能看到,单页面应用就是把整个应用程序发送到浏览器,在浏览器里面去运行这个程序。所以相比与一个html文件,一旦应用上规模,体积也相应的会很大。这就牵扯出chunk,把应用分块的概念。第一次请求的时候只把调度部分、首页以及相关几个页面传入到浏览器,后续请求到了没有传输的页面时,再把后续文件传输过来。

由于我们的页面都是在一个网页中,本质上是传输了一个web服务器到浏览器中,在导航时,由js控制渲染。所以过渡效果、过滤器、请求管道、中间件、web服务器具有的东西在网页中实现也就有了可能。

但单页面应用SPA,这个web服务器一切都要归功于浏览器提供的那个关键的功能,History API

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/772228.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nginx批量封禁黑名单ip

nginx批量封禁黑名单ip昨天搞到差不多1点,今天又是忙到6点半,连我领导都说“搞得我们加一好憔悴呀”。有很长一段时间没更新博客了,想着怎么做个人IP。。。谋出路一、需求介绍废话少说,需求就是怎么批量封禁别人给来的一大堆黑名单ip。甲方每天不定期发来几百、上千个ip,我…

【基础教程】Tutorial on Pytorch 结合官方基础文档和个人经验

参考与前言 此教程首次书写于2021年12月份 至 2022年4月份间不断补充;阅读本文时可以对着代码运行查看官方网址:https://pytorch.org/tutorials/ 【基本从这里翻译而来 更简洁版+碎碎念】 https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html#sphx-glr-begi…

kettle从入门到精通 第八十一课 ETL之kettle kettle中的json对象字段写入postgresql中的json字段正确姿势

1、上一节可讲解了如何将json数据写入pg数据库表中的json字段,虽然实现了效果,但若客户继续使用表输出步骤则仍然无法解决问题。 正确的的解决方式是设置数据库连接参数stringtype=unspecified 2、stringtype=unspecified 参数的作用: 当设置为 unspecified 时,pg JDBC 驱动…

C++文件系统操作6 - 跨平台实现文件和文件夹的拷贝

1. 关键词 2. fileutil.h 3. fileutil.cpp 4. filesystem_win.h 5. filesystem_win.cpp 6. filesystem_unix.cpp 7. 源码地址1. 关键词 C++ 文件系统操作 拷贝文件 拷贝文件夹 跨平台 2. fileutil.h#pragma once#include <string> #include <cstdio> #include <…

ArchLinux 问题集锦

电脑使用Android的网络 sudo pacman -S usb_modeswitch 手机打开网络共享 Android使用usb连接电脑网络 yay -S gnirehte gnirehtet runAndroid 投屏到电脑 sudo pacman -S scrcpy scrcpyPlasma KDE Screen Locking Picture of the Day 每日一图的图片缓存位置~/.cache/plasma_e…

Manjaro Linux安装

安装分区以及配置方案 manjaro 安装分区以及配置方案 根分区/:看成Windows的C分区,在重装系统时只格式化根分区/ /home分区:要存放用户目录及用户日积月累的数据,要尽量大些 /boot:不使用 UEFI 时,/boot 的建议大小时 200 MB,使用 UEFI 时,需要至少 512 MiB 空间 /var:…

雷达气象学(1)——雷达电磁波的散射

目录1.0 电磁波的特征1.1 散射的概念及类型1.2 散射函数——表示粒子的散射能力1.3 瑞利后向散射函数1.4 后向散射截面——更好地表示粒子的散射能力1.5 反射率因子 1.0 电磁波的特征 雷达的探测方式为电磁波。电磁波是在空间传播的电场和磁场两者结合,它在时空上呈现正弦与余…

雷达气象学(2)——雷达电磁波的衰减

目录2.1 衰减的概念2.2 气体对电磁波的衰减2.3 云对电磁波的衰减2.4 雨对电磁波的衰减2.5 雪对电磁波的衰减2.5.1 干雪对电磁波的衰减2.5.2 湿雪对电磁波的衰减2.6 冰雹对电磁波的衰减参考文献 2.1 衰减的概念 衰减是电磁波能量沿传播路径减弱的现象。造成衰减的原因是当电磁波…

将手机作为服务器运行docker服务

前言 目前手机的配置并不低,即使是2019年生产的一加七Pro,配置也有12+256,CPU是骁龙855,作为服务器运行着配置绰绰有余了,二手的价格现在是400左右也能接受。相对于是自带ups电源的便携低耗docker服务器,还能同时使用安卓系统,配上adb远程做云手机也可以。 要想原生支持…

Profinet远程IO模块:模拟量模块_安装与接线说明

Profinet远程IO模块由兴达易控研发,包含耦合器、多种I/O模块和辅助模块如:PROFINET、EtherCAT、Ethernet/IP、Cclink IE以及modbus/TCP等。支持多种通讯协议,提供多种数字量和模拟量输入输出模块,适用于不同现场需求。安装简便,需正确接线并检查电源。XD系列插片式远程 IO…