首页 > 其他分享 >后端说,单页面SPA和前端路由是怎么回事

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

时间:2024-07-26 23:07:30浏览次数:15  
标签:浏览器 url route SPA 导航 page 路由 页面

没有请求的路由

在传统开发中,浏览器点击一个超链接,就会像后端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

当然,这里还没有处理复制一个url到新标签页打开的功能。但这其实很简单,就是首次跳转根据location来调用route.routeTo("地址栏的url")

标签:浏览器,url,route,SPA,导航,page,路由,页面
From: https://www.cnblogs.com/ggtc/p/18322846

相关文章

  • [二、状态管理]3管理应用拥有的状态(2)LocalStorage:页面级UI状态存储
    LocalStorage是页面级的UI状态存储,通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage也可以在UIAbility实例内,在页面间共享状态。本文仅介绍LocalStorage使用场景和相关的装饰器:@LocalStorageProp和@LocalStorageLink。说明本模块从APIver......
  • 空间反演对称性 (Spatial Inversion Symmetry) 和非线性响应 (Non-linear Response)
    我们定义一次宇称变换(paritytransformation)为反转所有坐标:\[\mathcal{P}:\begin{pmatrix}x\\y\\z\end{pmatrix}\rightarrow\begin{pmatrix}-x\\-y\\-z\end{pmatrix}\]如果在一维世界中,宇称变换就像是透过“镜子”看这个世界;在三维世界中,则是将全部体系对于......
  • 如何解决“必须首先将控件添加到页面”的问题。机队错误?
    我正在尝试使用flet制作janggi(国际象棋变体)游戏。由于我必须在多个地方使用该板,因此我创建了一个名为janggiBoard的类,它继承自ft.Stack。我想对片段使用拖放操作,因此我按照flet文档中的Solitaire教程进行操作。当我尝试执行“move_on_top”部分时出现问题。在Solitaire......
  • 06_sparkStreaming
    SparkStreamingsparkStreaming用于处理流式数据,其中输入数据源包括Kafka、Flume、HDFS等;结果输出目的地址包括HDFS、数据库。SparkCore对应RDD;SparkSQL对应DataFrame/DataSet;SparkStreaming对应DStream(离散化数据流),DStream是对RDD在实时数据处理场景的一种封装。......
  • Spark+实例解读
    第一部分Spark入门第二部分SparkCore2RDD2.1转换算子-mapmap是将RDD的数据一条条处理,返回新的RDD#定义方法defadd(data):returndata*10print(rdd.map(add).collect)#定义lamabda表达式rdd.map(lambdadata:data*10)2.2转换算子-flatMapflatMap对RDD......
  • 运行 Spark-Shell 程序时出现错误
    我正在尝试创建SparkShell程序,但在运行时出现错误。下面是我正在执行的代码。frompyspark.sqlimport*frompysparkimportSparkConffromlib.loggerimportLog4j#conf=SparkConf()#conf.set("spark.executor.extraJavaOptions","-Dlog4j.configuration=f......
  • 将多个文件并行读取到 Pyspark 中的单独数据帧中
    我正在尝试将大型txt文件读入数据帧。每个文件大小为10-15GB,因为IO需要很长时间。我想并行读取多个文件并将它们放入单独的数据帧中。我尝试了下面的代码frommultiprocessing.poolimportThreadPooldefread_file(file_path):returnspark.read.csv(file......
  • 前端路由快速上手-React-Router
    1.前端路由简介前端路由是一种在单页面应用(SPA)中实现页面跳转的技术,它允许我们通过改变URL地址而无需重新加载页面来显示不同的内容。在前端路由中,每个路径(path)都对应一个组件(component),当访问特定的路径时,对应的组件就会在页面上渲染。2.创建路由开发环境要开始使用Rea......
  • 为什么 SpaceView3D 绘制处理程序不显示我的材质和光线?
    defdraw_callback_px(self,context):font_id=0#A4dimensionsat300DPIa4_width=707a4_height=1000region_width=context.region.widthregion_height=context.region.heightx_offset=(region_width-a4_width)/2......
  • BGP(Border Gateway Protocol,边界网关协议)劫持是指恶意或非法地篡改BGP路由信息的行为
    BGP(BorderGatewayProtocol,边界网关协议)劫持是指恶意或非法地篡改BGP路由信息的行为。BGP是互联网上用来交换路由信息的主要协议之一,它负责决定网络数据包应该如何从一个网络路由到另一个网络。BGP劫持可以分为两种主要类型:前缀劫持(PrefixHijacking):在前缀劫持中,攻击者发送......