1 引言
我发现一件事情,就是如果写一篇记录学习的博客,有时候是关于一些误入牛角尖的问题,真的只有有过相同经历的能够切身体会理解,所以能点赞收藏的小伙伴,真的给我一种知音的感觉
小菜鸡如果说错了,烦请指点。问题的起源偶然发现,在我学习的项目中,多页面应用之间的跳转都是通过 <form>
表单或者 <a/>
标签改变 URL,进行页面之间的跳转。而在单页面应用中都是通过前端路由的,明显使用路由跳转更为便捷。
然后问题就来了,请仔细看一下 SPA (Single Page Application) 和 MPA(Mulpile Page Application) 项目的 URL
MPA:
SPA:
从上可以看出一些差别, MPA 端口后面跟着的就是要请求的 html
文件,?
后面跟着请求的参数。 而 SAP 端口后面跟着的就"路径",也不知道请求的是啥资源,html
文件? json
文件?或者其他?这就很让人疑惑。其实这里就可以引入一个概念——渲染方式
2 渲染
2.1 CSR 和 SSR
SPA 全称 Single Page Application,即单页面应用。一般也称为 CSR(Client Side Render),即客户端渲染。它所需的资源,如 HTML、CSS 和 JS 等,在一次请求中就加载完成,也就是不需刷新地动态加载。浏览器(Client)渲染顾名思义就是所有的页面渲染、逻辑处理、页面路由、接口请求均是在浏览器中发生。对于 SPA 来说,页面的切换就是视图之间的切换。
MPA(Mulpile Page Application),即多页面应用。一般都是 SSR 全称 Server Side Render,服务器端渲染。服务端渲染则是在服务端完成页面的渲染,在服务端完成页面模板、数据填充、页面渲染,然后将完整的HTML内容返回给到浏览器。由于所有的渲染工作都在服务端完成,因此网站的首屏时间,白屏时间也会比较长,同时,对于服务端的负载要求也会比较高。
2.2 前端框架的 SPA
如此,引言里边的疑惑就可以窥探到一点真相了,应该可以这样去理解这件事——例如利用 React 脚手架 create-react-app
创建一个单页面应用 (SPA),在 vcCode 终端运行 npm start
,就会启动项目,一般是占用3000端口,在浏览器地址栏输入http://localhost:3000
就会可以看到初始页面,原因应该是 vcCode,会在本地开启一个服务器,在客户端也就是浏览器访问本地的3000端口,就会把所需的资源一次性全给客户端,这样例如访问localhost:3000/ldp.json
,就会去项目下的public文件夹下寻找 ldp.json
文件 没有指定要请求什么资源的时候,会默认为项目的 public 文件夹下的index.html,至于展示index.html
需要啥资源都能清楚,这也就依赖于 webpack 的打包功能了。,然后在 index.html
文件里面一般会有一个 div
标签作为容器,src 目录下会有一个 index.js 文件,作为入口文件,里面可以写 JSX 语法,然后渲染成真实 DOM 到容器里面。再细说就是 React 的组件化思想了。
3 路由
3.1 后端路由
通过用户请求的 URL 导航到具体的 HTML 页面;每跳转到不同的 URL,都是重新访问服务端,如果是静态资源的获取,那么这个函数就是一个文件读取操作,如果是较为复杂的动态资源的获取,那么这个函数就是一个链接数据库,增删改查数据并进行处理的数据操作。下面自个手写了一个简单的获取静态页面的后端路由。
// 1. 引入 http,fs 模块
const http = require("http");
const fs = require('fs')
//创建服务
http.createServer(function (req, res) {
// 设置 HTTP 头部,状态码是 200,文件类型是 html,字符集是 utf8
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
if(req.url == '/test2') {
let buf = fs.readFileSync("./test2.html");
res.write(buf);
} else if (req.url == '/test1') {
let buf = fs.readFileSync("./test1.html");
res.write(buf);
} else {
res.write(req.headers.host+req.url)
}
// 结束响应
res.end();
}).listen(3000); // 监听3000端口
复制代码
3.2 前端路由
3.2.1 前端路由的由来
SPA 的出现离不开 Ajax,SPA 的一大特征就是在与用户的交互过程中,不再需要重新刷新页面,而通过 Ajax 向服务端发送请求,不改变 URL,能够实现局部刷新,使得页面显示更加流畅
但是SPA 中用户的交互是通过 JS 改变 HTML 内容来实现的,页面本身的 URL 并没有变化,这就导致一些不方便:
- SPA 无法记住用户的操作记录,假如你想分享一个详情页面的文章链接给你的好朋友,但是由于你的操作没有导致 URL 的变化,你的朋友在浏览器复制粘贴你的链接,打开的是首页而不是详情页。
- SPA 中虽然由于业务的不同会有多种页面展示形式,但只有一个 URL,对 SEO 不友好,不方便搜索引擎进行收录
不改变 URL,可以避免页面的全局重新渲染,但是又会导致上面的问题,难搞哦,前端路由就是为了解决这两者冲突的问题
3.2.2 hsah
hsah路由是一种比较早的路由,熟悉 URL 构成的小伙伴一定知道 #
后的内容 Web 服务都会自动忽略,可以通过监听onhashchange
事件获取到hash
的改变,通过 window.location.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>Document</title>
</head>
<body>
<ul>
<li><a href="#/">/</a></li>
<li><a href="#/page1">page1</a></li>
<li><a href="#/page2">page2</a></li>
</ul>
<div id="inner">1</div>
<script>
let sum=1
window.addEventListener("hashchange", () => {
let result = document.getElementById('inner')
sum++
result.innerHTML = sum
});
</script>
</body>
</html>
复制代码
上面的代码就简单的实现了根据不同的 hash 路由在页面展现不同的内容
3.2.3 html5
在HTML5之前,浏览器的history可以通过以下几个方法,在多个页面之间跳转
history.go(n); // 前进n页,可为负数
history.forward(); // 前进一页
history.back(); // 后退一
复制代码
在 HTML5 的规范中,history 新增了以下几个 API:
history.pushState(); // 添加新的状态到历史状态栈
history.replaceState(); // 用新的状态代替当前状态
history.state // 返回当前状态对象
复制代码
pushState
和 replaceState
的参数都有三个
- 状态对象,通过pushState () 创建新的历史记录条目。状态对象发生变化,popstate事件就会被触发
- 标题,大部分浏览器会忽略这个参数
- URL ,该参数定义了新的历史URL记录,
pushState()
后浏览器并不会立即加载这个URL
我监听了两个按钮的点击事件,具体代码如下:
button[3].addEventListener("click",() => {//按钮1
var stateObj = { foo: 'bar' };
var stateObj2 = {bar:'foo'}
history.pushState(stateObj, 'page 2', '2.html');
history.pushState(stateObj2, 'page 2', '3.html');
console.log(history.state)
})
button[4].addEventListener("click",() => {//按钮2
history.pushState({page: 1}, 'title 1', '?page=1');
history.pushState({page: 2}, 'title 2', '?page=2');
history.replaceState({page: 3}, 'title 3', '?page=3');
console.log(history.state)
})
复制代码
一开始的 URL 是这样的
点击按钮一:
此时 history,state
输出是 {bar: "foo"}
,我在点击浏览器回退按钮,URL为:
在浏览器打印此时的 history,state
,为 {foo: "bar"}
点击按钮二:
回退后为:
值得注意的是,虽然点击按钮 URL 发生了变化,但是页面并不会刷新变化,这就是路由的雏形了。
3.2.4 前端框架路由
框架的路由里面水太深,我暂时把握不住,后面等自己将 Vue 和 React 的学习进行总结的时候会考虑记录,但是能够知道,前端框架的路由都是对前端路由的封装,因为最近学 React 比较多,还是拿它来举例。
react-router 的工作方式,是在组件树顶层放一个 Router 组件,然后在组件树中散落着很多 Route 组件(注意比 Router 少一个“r”),顶层的 Router 组件负责分析监听 URL 的变化,在它保护伞之下的 Route 组件可以直接读取这些信息。
在 index.js 文件里面就渲染 <App/>
组件,<App>
组件如图:
页面跳转就要使用 Link
标签 <Link to='/'>Home</Link>
,更改 URL,而不做页面跳转,其实 <Link>
,是封装后的 <a>
关于 BrowserRouter 和 HashRouter
- 留下历史记录的底层原理不一样
- BrowserRouter使用的是H5的history API,不兼容IE9及一下版本
- HashRouter使用的URL的哈希值(/#)#后面的内容是不会传给服务器的
- 刷新后对路由state参数的影响
- BrowserRouter没有影响,因为state保存在history中,浏览器不关闭,不清理缓存,history就能用
- HashRouter刷新后会导致state参数的丢失,因为state再地址上面不放参数,然后HashRouter又不能调用history
4 多页面路由
其实我最初的疑惑还有一个就是,在多页面应用中能不能使用路由呢?
谷歌了一下,发现好像有这种解决方案呢?感兴趣的同学可以搜索多页面多路由,进行了解,我大概看了一下,都是基于 Vue-cli,Vue 我都有段时间没碰了,那我走?哈哈哈哈!待以后拾起来的时候再去看看,立个 flag 搁这。
参考: