点击上方 React中文社区,关注公众号
回复加群,加入技术交流群交流
背景
单页应用(Single Page Application,简称SPA)是一种现代Web应用程序架构,通过动态重载页面中的部分内容来提供更流畅和更响应式的用户体验。由于SPA在客户端(用户的浏览器)运行大量的JavaScript代码,并且与传统的多页应用不同,它不会每次操作都从服务器重新加载整个页面内容,因此在性能和用户体验上有显著优势。然而这种应用也存在一定弊端,譬如当服务端更新时(接口请求体和响应体结构发生变化),用户所使用的网页静态资源没有同步更新,就有可能导致报错。
前言
在传统的多页Web应用中,每次用户访问页面时,都会从服务器获取最新的页面和资源,因此版本更新相对简单,用户总是能获取到最新的版本。然而,SPA在首次加载后,前端的静态资源会缓存在浏览器内存中,且在整个使用过程中通常不会自动重新加载。这种特性意味着如果应用有新的版本发布,用户可能仍在使用旧版本,无法立即获得最新的功能、修复或安全更新。
怎么实现?
我们想实现这样一个效果,场景是:
攻城狮发版完成,客户端检测到有版本更新后给用户一个更新提示,让用户知道有新版本更新了
先来实现这个弹窗:
import { Modal } from 'antd';
function updateNotice() {
Modal.confirm({
title: '更新提示',
content: '检测到新版本,建议立即更新以确保平台正常使用。',
okText: '确认更新',
cancelText: '稍后更新',
onOk: () => {
window.location.reload();
},
});
}
方案一:比较构建文件的hash值
该方案需要webpack开启打包文件带上hash值,具体配置不在此处展开。
通过定期获取服务器的前端资源,匹配资源中的 <script>
标签,对比前后标签是否一致,来检测是否有新的版本发布。
// 存储当前脚本标签的哈希值集合
let scriptHashes = new Set();
let timer = undefined;
/**
* 从首页获取脚本标签的哈希值集合
* @returns {Promise<Set<string>>} 返回包含脚本标签的哈希值的集合
*/
async function fetchScriptHashes() {
// 获取首页HTML内容
const html = await fetch('/').then((res) => res.text());
// 正则表达式匹配所有<script>标签
const scriptRegex = /<script(?:\s+[^>]*)?>(.*?)</script\s*>/gi;
// 获取匹配到的所有<script>标签内容
const scripts = html.match(scriptRegex) ?? [];
// 将脚本标签内容存入集合并返回
return new Set(scripts);
}
/**
* 比较当前脚本标签的哈希值集合与新获取的集合,检测是否有更新
*/
async function compareScriptHashes() {
// 获取新的脚本标签哈希值集合
const newScriptHashes = await fetchScriptHashes();
if (scriptHashes.size === 0) {
// 初次运行时,存储当前脚本标签哈希值
scriptHashes = newScriptHashes;
} else if (
scriptHashes.size !== newScriptHashes.size ||
![...scriptHashes].every((hash) => newScriptHashes.has(hash))
) {
// 如果脚本标签数量或内容发生变化,则认为有更新
console.info('更新了', {
oldScript: [...scriptHashes],
newScript: [...newScriptHashes],
});
// 清除定时器
clearInterval(timer);
// 提示用户更新
updateNotice();
} else {
// 没有更新
console.info('没更新', {
oldScript: [...scriptHashes],
});
}
}
// 每60秒检查一次是否有新的脚本标签更新
timer = setInterval(compareScriptHashes, 60000);
打印结果示例:
轮询效果:有新版本发布时,服务器资源更新,才会返回新的资源给客户端;资源未更新时,服务器返回304,不会返回资源,而是让客户端从本地缓存里获取,资源消耗相对较小。
方案二:利用HTTP协议的缓存机制,比较Etag或last-modified前后是否一致
本方案实现同方案一类似,但与方案一相比,方案二更直接地利用了 HTTP 协议提供的缓存控制机制,以确定页面是否发生了变化。
let versionTag = null; // 版本标识
let timer = undefined;
/**
* 获取首页的 ETag 或 Last-Modified 值,作为当前版本标识
* @returns {Promise<string|null>} 返回 ETag 或 Last-Modified 值
*/
const getVersionTag = async () => {
const response = await fetch('/', {
cache: 'no-cache',
});
return response.headers.get('etag') || response.headers.get('last-modified');
};
/**
* 比较当前的 ETag 或 Last-Modified 值与最新获取的值
*/
const compareTag = async () => {
const newVersionTag = await getVersionTag();
if (versionTag === null) {
// 初次运行时,存储当前的 ETag 或 Last-Modified 值
versionTag = newVersionTag;
} else if (versionTag !== newVersionTag) {
// 如果 ETag 或 Last-Modified 发生变化,则认为有更新
console.info('更新了', {
oldVersionTag: versionTag,
newVersionTag: newVersionTag,
});
// 清除定时器
clearInterval(timer);
// 提示用户更新
updateNotice();
} else {
// 没有更新
console.info('没更新', {
oldVersionTag: versionTag,
newVersionTag: newVersionTag,
});
}
};
// 每60秒检查一次是否有新的 ETag 或 Last-Modified 值
timer = setInterval(compareTag, 60000);
etag打印结果示例:
方案三:应用改造成PWA + service-worker方案
待补充...
总结
为了确保用户始终使用最新的版本并体验到最佳的功能和安全性,SPA应用需要实现版本检测和更新提示机制。当应用有新版本发布时,通过提示用户刷新页面或自动重新加载,以确保用户获取到最新的应用代码和资源。
送人玫瑰,手留余香,觉得有收获的朋友可以点赞,关注一波 ,我们组建了高级前端交流群,如果您热爱技术,想一起讨论技术,交流进步,不管是面试题,工作中的问题,难点热点都可以在交流群交流,为了拿到大Offer,邀请您进群,入群就送前端精选100本电子书以及 阿里面试前端精选资料 添加 下方小助手二维码或者扫描二维码 就可以进群。让我们一起学习进步.
推荐阅读
(点击标题可跳转阅读)
[极客前沿]-你不知道的 React 18 新特性
[极客前沿]-写给前端的 K8s 上手指南
[极客前沿]-写给前端的Docker上手指南
[面试必问]-你不知道的 React Hooks 那些糟心事
[面试必问]-一文彻底搞懂 React 调度机制原理
[面试必问]-一文彻底搞懂 React 合成事件原理
[面试必问]-全网最简单的React Hooks源码解析
[面试必问]-一文掌握 Webpack 编译流程
[面试必问]-全网最全 React16.0-16.8 特性总结
[架构分享]- 微前端qiankun+docker+nginx自动化部署[自我提升]-送给React开发者十九条性能优化建议
[大前端之路]-连前端都看得懂的《Nginx 入门指南》
[软实力提升]-金三银四,如何写一份面试官心中的简历
觉得本文对你有帮助?请分享给更多人
关注「React中文社区」加星标,每天进步
“在看和转发”就是最大的支持