前言
单页应用(Single Page Application,简称SPA)项目中,通过动态重载页面的部分内容来提高项目流畅性。然而该应用也存在弊端,当服务端发生更新,接口请求体和响应体结构发生变化,停留在客户端的项目依然访问的旧资源,未能同步更新,就可能导致项目报错。对于浏览器长时间保持打开的用户来说,当版本更新提示客户版本升级就很有必要。
实现方式
1. WebSocket实时通信
- 建立WebSocket连接,有新版本时服务器向客户发送通知
需要后端配合,后端回复说就不能前端自己搞嘛
2. 前端检测版本更新
- a) 比较构建文件的hash值
- b) 利用HTTP协议的Etag 或 Last-modified
js
const projectHome = '/'
// a) 比较构建文件的hash值
const getScriptHash = () => {
fetch(projectHome).then((res) => {
const html = res.text();
return html.match(/<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/i)[0]
})
}
// '\x3Cscript type="module" crossorigin src="/Lance-Element-Admin/assets/index-56a14e29.js">\x3C/script>'
// b: 利用HTTP协议的Etag 或 Last-modified
const getVersionTag = async () => {
return fetch(projectHome, { method: 'HEAD', cache: 'no-cache' })
.then(res => res.headers.get('etag') || res.headers.get('last-modified'))
.catch(e => {
console.error('Failed to fetch version tag', e)
return null
})
}
// etag: 'W/"67caae6b-578"' last-modified: 'Fri, 07 Mar 2025 08:29:31 GMT'
3. 前端检测触发时机/方式
- 通过
setInterval
,setTimeout
模拟 setInterval 轮询 - 监听
visibilitychange
tab切换显示 触发
以下使用的是我们的 getVersionTag
方法 来做校验为例
js
let timer = null
const doFetch = async () => {
const versionTag = await getVersionTag()
if (!versionTag) return
// 首次运行时不提示更新
if (!window.lastVersionTag) {
window.lastVersionTag = versionTag
return
}
if (window.lastVersionTag !== versionTag) {
stop()
console.log('项目更新了, 是时候刷新页面了!!!!')
// todo 提示用户项目更新操作 +++
}
}
const start = async (immediate = false) => {
if (immediate) {
await doFetch()
}
if (timer) stop()
timer = setInterval(doFetch, 5000)
}
const stop = () => {
clearInterval(timer)
}
function handleVisibilitychange() {
if (document.hidden) {
stop()
} else {
start(true)
}
}
document.addEventListener('visibilitychange', handleVisibilitychange)
start()
以上我们的 前端版本更新校验主要逻辑已经初步完成了, 但是作为一个优秀的你想到了避免阻塞主线程是不是可以用 Worker
起一个新的线程处理,来提升页面性能
Web Worker
1. 作用
- 避免主线程阻塞:将耗时任务(如复杂计算、大数据处理)移至后台线程,防止页面卡顿或无响应。
- 提升计算性能:利用多核 CPU 并行处理任务(如加密解密、图像/视频处理)。
- 异步处理 I/O 操作 :后台处理文件读写(通过
File API
)、网络请求(如大文件分片上传)。 - 实时数据处理:持续处理 WebSocket 推送的实时数据流(如金融行情、聊天消息)。
- 离线任务支持:结合 Service Worker 实现后台同步或离线缓存更新
2. Web Workder 创建方式
- 常规创建 Worker
javascript
// worker.js
self.onmessage = (e) => {
// 模拟处理任务
const result = e.data * 100
// 返回结果
self.postMessage(result)
}
js
// 主线程
const worker = new Worker('worker.js') // 指定 Worker 脚本路径
worker.postMessage({ data: 1 }) // 发送消息
// 接收消息
worker.onmessage = (e) => {
console.log('结果:', e.data)
}
// 手动终止 Worker
worker.terminate()
- 动态创建 Worker(通过 Blob/URL)
js
// 主线程
const code = `
self.onmessage = (e) => {
const result = e.data * 100
self.postMessage(result)
};
`
const blob = new Blob([code], { type: 'application/javascript' })
const url = URL.createObjectURL(blob)
const worker = new Worker(url)
worker.postMessage({ data: 1 }) // 发送消息
// 接收消息
worker.onmessage = (e) => {
console.log('结果:', e.data)
}
// 手动终止 Worker
worker.terminate()
通过Web Worker 实现版本检测
ts
// CheckUpdates/utils.ts
// 创建worker
export const createWorker = (func: () => void, deps?: Array<() => void>) => {
// work 依赖 fns
const depsFncStr = `${deps?.map(dep => dep.toString()).join(';\n\n') || ''}`
const blob = new Blob(
[`
${depsFncStr};
(${func.toString()})();
`],
{
type: 'application/javascript'
})
return new Worker(window.URL.createObjectURL(blob))
}
// 动态创建的worker
export const createWorkFn = () => {
const opts = {
immediate: true,
intervalTime: 5000, // 毫秒
fetchUrl: '/'
}
let timer: any = null
const stop = () => {
clearInterval(timer)
}
const temp: Worker = self as any
let lastVersionTag = ''
const getVersionTag = async () => {
return fetch(opts.fetchUrl, { method: 'HEAD', cache: 'no-cache' })
.then(res => res.headers.get('etag') || res.headers.get('last-modified'))
.catch(e => {
console.error('Failed to fetch version tag', e)
return null
})
}
const doFetch = async () => {
const versionTag = await getVersionTag()
if (!versionTag) return
// 首次运行时不提示更新
if (!lastVersionTag) {
lastVersionTag = versionTag
return
}
if (lastVersionTag !== versionTag) {
stop()
temp.postMessage({ type: 'showNotice', data: versionTag })
}
}
temp.addEventListener('message', async (event: any) => {
// { type: 'start' | 'stop' }
switch (event.data.type) {
case 'start':
{
const data = event.data.data
if (data.intervalTime) opts.intervalTime = data.intervalTime
if (data.fetchUrl) opts.fetchUrl = data.fetchUrl
if (data.immediate) {
await doFetch()
}
if (timer) stop()
timer = setInterval(doFetch, opts.intervalTime)
}
break
case 'stop': {
stop()
}
break
}
})
return temp
}
ts
// CheckUpdates
import { createWorker, createWorkFn } from './utils'
const opts = {
intervalTime: 5 * 60 * 1000, // 5min
fetchUrl: entrance
// immediate: false
}
const worker = createWorker(createWorkFn, [])
worker.addEventListener('message', (e: any) => {
// e.data: {type: 'showNotice', data: 'version'}
handleNotice()
})
const start = (immediate = false) => {
worker.postMessage({ type: 'start', data: { ...opts, immediate } })
}
const stop = () => {
worker.postMessage({ type: 'stop' })
}
const handleNotice = () => {
console.log('项目更新了, 是时候刷新页面了!!!!')
// todo 提示用户项目更新操作 +++
}
function handleVisibilitychange() {
if (document.hidden) {
stop()
} else {
start(true)
}
}
start()
document.addEventListener('visibilitychange', handleVisibilitychange)
以上就是完整的检测版本更新的逻辑,完整的项目为 项目链接
文件地址: src/layout/components/CheckUpdates
该项目是 Vue3 + Ts + Element-plus & Vite
搭建,兼容了手机端 开箱即用的后台管理项目 如果觉得不错希望能得到各位大佬的star