后端说,单页面SPA和前端路由是怎么回事
没有请求的路由
在传统开发中,浏览器点击一个超链接,就会像后端web服务器发送一个html文档请求,然后页面刷新。但开始单页面开发后,就完全不同了。
单页面?这个概念难以理解。我用一个js作为整个web应用,然后再这个js中操作dom变化,以此来实现页面变化。这不叫单页面吗?这叫!但不完善,因为这种方法破坏了浏览器自带的导航功能。比如前进,后退。所以单页面前端应用要解决两件事内容变化、导航变化。这是现代前端成立的基础。
想必初次接触vue-router
和nuxt
的人很多对前端路由困惑。明明浏览器地址栏的链接变了,为什么浏览器却没有发送请求出去?至少我是很疑惑的。
这要归功于一个浏览器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("这里可以传参数");
}
})
效果
可以看到,切换页面时,地址栏变了,但并没有网络请求发出
完整代码
<!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
后端说,单页面SPA和前端路由是怎么回事的更多相关文章
- 单页面SPA应用路由原理 history hash
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8&quo ...
- vue 单页面(SPA) history模式调用微信jssdk 跳转后偶尔 "invalid signature"错误解决方案
项目背景 vue-cli生成的单页面项目,router使用history模式.产品会在公众号内使用,需要添加微信JSSDK,做分享相关配置. 遇到的问题 相关配置与JS接口安全域名都已经ok,发布后, ...
- SPA单页面应用和MPA多页面应用(转)
原文:https://www.jianshu.com/p/a02eb15d2d70 单页面应用 第一次进入页面时会请求一个html文件,刷新清除一下,切换到其他组件,此时路径也相应变化,但是并没有新的 ...
- SPA(单页面应用)和MPA(多页面应用)
话不多说,直接看图,一目了然
- 使用react-router实现单页面应用路由
这是Webpack+React系列配置过程记录的第二篇.其他内容请参考: 第一篇:使用webpack.babel.react.antdesign配置单页面应用开发环境 第二篇:使用react-rout ...
- Angular——单页面与路由的使用
单页面 SPA(Single Page Application)指的是通单一页面展示所有功能,通过Ajax动态获取数据然后进行实时渲染,结合CSS3动画模仿原生App交互,然后再进行打包(使用工具把W ...
- [后端人员耍前端系列]KnockoutJs篇:使用WebApi+Bootstrap+KnockoutJs打造单页面程序
一.前言 在前一个专题快速介绍了KnockoutJs相关知识点,也写了一些简单例子,希望通过这些例子大家可以快速入门KnockoutJs.为了让大家可以清楚地看到KnockoutJs在实际项目中的应用 ...
- 前端单页面富应用(SPA)的实现
一. 什么是单页面富应用? 单页面应用:Single Page Application 概念:Web应用即使不刷新也在不同的页面间切换,解决浏览器前进.后退等机制被破坏等问题.并且页面访问会被浏览器保 ...
- (转)前端:将网站打造成单页面应用SPA
前端:将网站打造成单页面应用SPA(一) Coffce 680 6月19日 发布 推荐 6 推荐 收藏 85 收藏,3.1k 浏览 前言 不知你有没有发现,像Github.百度.微博等这些大站,已经不 ...
- 前端:将网站打造成单页面应用SPA
前端:将网站打造成单页面应用SPA 前言 不知你有没有发现,像Github.百度.微博等这些大站,已经不再使用普通的a标签做跳转了.他们大多使用Ajax请求替代了a标签的默认跳转,然后使用HTML ...
随机推荐
- 全网最全100个AI工具导航网站合集
随着ChatGPT年前的爆火,人工智能也变成当今最热门的领域之一,它正在改变着我们的生活和工作方式.无论你是想要学习人工智能的基础知识,还是想要利用人工智能来提升你的业务效率和创新能力,都需要找到合适 ...
- k8s中的pod更新策略
StatefulSet(有状态集,缩写为sts)常用于部署有状态的且需要有序启动的应用程序,比如在进行SpringCloud项目容器化时,Eureka的部署是比较适合用StatefulSet部署方式的 ...
- Qt-FFmpeg开发-打开本地摄像头(6)
音视频/FFmpeg #Qt Qt-FFmpeg开发-打开本地摄像头[软解码+ OpenGL显示YUV] 目录 音视频/FFmpeg #Qt Qt-FFmpeg开发-打开本地摄像头[软解码+ Open ...
- 面试题一《swift和oc的区别》
一.来源 这道题来自网上一篇文章<100家公司iOS面试题管理>,这份题目虽然题目质量不高,但是覆盖面比较全,有学习的价值 二.解析 1.swift 比 OC更年轻,这意味着 swift ...
- 用Python脚本迁移MongoDB数据到金仓-kingbase数据库
1.首先需要明确MongoDB与kingbase的对应关系,collection相当于table,filed相当于字段,根据这个对应关系创建表: 此次迁移的MongoDB里的数据字段是:_id(自动生 ...
- vue3+vant 引入Dialog Toast都会失败报错not defined
今天在封装vant组件的时候,刚好要用到toast提示信息的组件,索性就按照官网提供的引入方法进行正常的引入,嘿,好家伙,一顿操作下来后发现竟然报Toast未定义,这就纳闷了,明明步骤都是对的啊,所以 ...
- 算法学习笔记(10): BSGS算法及其扩展算法
BSGS算法及其扩展算法 BSGS算法 所谓 Baby Step, Giant Step 算法,也就是暴力算法的优化 用于求出已知 \(a, b, p\), 且 \(p\) 为质数 时 \(a^x \ ...
- INFINI Labs 产品更新 | 统一版本号 1.22.0
INFINI Labs 产品又更新啦~,包括 Console,Gateway,Loadgen,Agent 1.22.0.为了避免版本不同带来的困扰,以后发布均统一版本号,此次版本重点修复历史遗留 Bu ...
- 项目管理--PMBOK 读书笔记(7)【项目成本管理】
1.成本术语: 2.三点估算(PERT): 平均估算值=(最可能持续时间*4+最乐观+最悲观)/6 标准差=(最乐观-最悲观)/6 68.26%.95.46%.99.73% 3.估算成本的工具:质量成 ...
- 向Web服务器端上传文件
server.py import flaskapp = flask.Flask(__name__)@app.route('/upload', methods=['POST'])def uploadFi ...