本来只是想学习 React-Router v6 ,没有想到,带出了这么多东西。前后端路由有什么区别?SPA与MPA的是什么?在了解到前端路之后又发现单页面于应用与多页面应用的不同之处,以及 .nextjs 数据抓取选择CSR、SSR、SSG、ISP不同形式也是有区别的。
路由这个概念最早出现在后端,在服务端中路由描述的是 URL 与处理函数之间的映射关系。对于服务器来说,当接收到客户端发来的HTTP请求,就会根据所请求的相应URL,来找到相应的映射函数,然后执行该函数,并将函数的返回值发送给客户端。(该过程又称之为后端路由或 服务端路由)
大致流程如图所示:
早期前端路由主要呈现的是多页面应用(MPA)的形式,每个HTML界面通过自己独有的URL,向服务端发起请求。这导致用户每发起一次不同的请求,服务器就要解析不同的URL,切换资源加载慢影响用户体验。
而随着前后端的分离,AJAX的出现带来了无刷新加载的优势。现在的前端路由不同于传统路由,它不在需要服务器解析,而是通过 Hash 函数或者 History API 来实现。在前端开发中,我们可以使用路由设置访问路径,并根据路径与组件的映射关系切换不同的组件,而这个过程都是在同一个页面中实现的,不涉及HTML页面之间的跳转,这也就是我们常说的单页应用(SPA)。
AJAX工作大致流程如图所示:
前端路由:不向后台发送请求,不刷新页面,前后端分离( 无刷新加载 )
后端路由:向服务器发送请求,会刷新页面,前后端不能分离( 需要刷新加载 )
SPA全称(single-page application),翻译过来就是单页应用。
单页应用(SPA):它是一种特殊的web应用。将所有的活动局限于一个Web页面中,仅在该Web页面初始化时加载相应的HTML、JavaScript和CSS。 一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转。取而代之的是利用JavaScript动态的变换HTML的内容, 从而实现UI与用户的交互。(该过程就类似我们常说的客户端渲染,又称 CSR )
SPA.优点:
SPA.缺点:
MPA全称(mutiple-page application),翻译过来就是多页面应用。
多页面应用(MPA):每个页面都是相对独立的,它们都拥有各自独立的URL,当我们访问一个页面时,需要重新加载公共资源文件(HTML、Javascript、CSS)。
即当MPA对您的输入作出反应或必须显示一些新内容时,它会从服务器请求一个新的HTML页面。在接收到标记之后,浏览器将呈现新页面,并重新加载它。(该过程类似我们常说的服务端渲染,又称 SSR)
MPA.优点:
MPA缺点:
总体来说
:不难发现其实spa与mpa之间的优缺点都是互补的,你的缺点就是我的优点。
疑问:SPA 这么好是不是MPA就没有使用了呢?如果有那么什么情况下会使用到 MAP ?
其实从以下几个方面不难看出它们之间的区别,以及选择什么合适。
在了解到SPA与MPA后,发现其与渲染模式是息息相关于的。
1.CSR 全称(Client-Side Rendering) 客户端渲染:在每次渲染之后获取数据
页面的渲染其实就是浏览器将HTML文本转化为页面帧的过程。而如今我们大部分WEB应用都是使用 JavaScript 框架(Vue、React、Angular)进行页面渲染的,也就是说,求在执行 JavaScript 脚本的时候,HTML页面已经开始解析并且构建DOM树了,JavaScript 脚本只是动态的改变 DOM 树的结构,使得页面动态渲染。这个过程就叫客户端渲染。
CSR大致流程如图
SSR 全称(Server-Side Rendering) 服务器端渲染:在每次渲染之前获取数据
服务端渲染就是在浏览器请求页面URL时,服务端直接将我们需要的HTML文本组装好,并返回给浏览器,这个HTML文本被浏览器解析之后,不需要经过JavaScript脚本的执行,可直接构建出完整的DOM树并展示页面中。这个服务端组装的过程,叫做服务端渲染。
而从服务器端请求URL获取数据的过程,是在页面加载之前完成的,该过程将首先运行特殊函数映射相关数据,并返回给客户端。在返回前存在一定(映射)延迟。
SSR大致流程图
SSG 全称(Static Site Generator )静态站点生成器:在构建时获取一次数据
解析是在构建时执行的,当发出请求时,html 将静态存储,直接发送回客户端。
ISR 全称(Incremental Static Regeneration) 增量静态再生:
数据在构建时获取一次,在一定冷却时间后再次获取,并在第二次访问时提供。
增量静态再生是SSG和SSR的组合,它是静态服务的,但在特定的时间和条件下,页面将重新构建并再次从API获取数据
参考&学习
在了解SPA后不难发现其实它的原理是通过以下两种模式来实现的
早起的前端路由实现就是基于location.hash来实现的,location.hash就是路由#后面的内容,其原理就是通过hashchange监听#后面的内容的变化来进行页面更新。hash模式是利用浏览器不会对#后面的路径对服务端发起请求。
Hash 主要特性:
根据它的特性可以用原始的JS来实现Hash路由
实现思路:运用hash特性,通过触发 hashchange 事件实现界面(UI),更新到 #/后面对应的内容
原生JS实现Hash路由方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>原生JS实现的前端路由</title>
</head>
<body>
<ul>
<li><a href="#/home">首页</a></li>
<li><a href="#/user">用户中心</a></li>
<li><a href="#/login">登录</a></li>
</ul>
<div id="view"></div>
</body>
<script>
let view = null
// 1.当初始的 HTML 文档被完全加载和解析完成之后,**DOMContentLoaded **事件被触发
window.addEventListener('DOMContentLoaded',onload)
// 3.监听hash变化
window.addEventListener('hashchange',onHashChange)
// 2.初次赋值
function onload(){
view = document.getElementById('view')
onHashChange()
}
function onHashChange(){
switch (location.hash){
case '#/home':
view.innerHTML = '首页'
break;
case '#/user':
view.innerHTML = '用户中心'
break;
case '#/login':
view.innerHTML = '登录'
break;
}
}
</script>
</html>
history 提供了 pushState 和 replaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新。 history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同:
那应该怎样才能触发popstate事件呢?
原生 history APi 实现案例
<p id="example">
<a href="/name" title="name">name</a>
<a href="/age" title="age">age</a>?
</p>
<div class="main" id="main"></div>
<script>
;(function(){
var examplebox = document.getElementById('example')
var mainbox = document.getElementById('main')
examplebox.addEventListener('click', function(e){
e.preventDefault()
var elm = e.target
var uri = elm.href
var tlt = elm.title
history.pushState({path:uri,title:tlt}, null, uri)
mainbox.innerHTML = 'current page is '+tlt
})
window.addEventListener('popstate',function(e){
var state = e.state
mainbox.innerHTML = 'current page is ' + state.title
})
})()
</script>
核心思想:通过改变URL来触发pushState事件,达到同步页面UI的目的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Vue中的History 模式的实现</h1>
<button id="myBtn">改变Url</button>
<script>
const myBtn = document.getElementById('myBtn')
window.addEventListener('DOMContentLoaded', () => {
console.log('path:', location.pathname);
})
myBtn.addEventListener('click', () => {
const state = {
'page_id': 1,
'user_id': 5
}
const title = 'gotoser'
const url = 'index.html' //切换到的另一个 HTML文件
history.pushState(state, title, url)
console.log('切换到了路由:', 'user');
})
window.onpopstate = (e) => {
console.log('onpopstate', e.state, location.pathname);
}
</script>
</body>
</html>
最终效果如图:
在react-router-dom中直接引入 HashRouter 即可
import { HashRouter as Router,Routes,Route,Link } from 'react-router
import HomePage from './pages/HomePage/index';
import UserPage from "./pages/UserPage/index"
import LoginPage from "./pages/LoginPage/index"
function App() {
return (
<div className="App">
<Router>
<Link to="/">首页</Link>
<Link to="/user">用户中心</Link>
<Link to="/login">登录</Link>
</Router>
</div>
);
}
React-router 原生写法待完善…
HashRouter 是不需要服务端渲染的,靠浏览器的 # 来区分 path 就可以,而BrowserRouter 需要服务器端对不同的URL返回不同的HTML。需要后端配置
BrowserRouter 使用 HTML5 History API 其中包含 pushState、replaceState 、popstate 等事件,让页面UI同步与之URL。
HashRouter 不支持 location.key 与 location.state 动态路由跳转需要通过 ?传递参数。
HashRouter在web浏览器中使用,当URL由于某种原因不应该(或不能)发送到服务器时。这可能发生在某些共享托管场景中,您无法完全控制服务器。在这些情况下,HashRouter
可以将当前位置存储在当前URL的散列’部分,因此它永远不会被发送到服务器。所以事件开发环境中不推荐使用。
参考&学习
SPA实现原理参考:https://github.com/youngwind/blog/issues/109
本文内容主要是学习笔记总结,内容来源于网络