react-admin 应用热更新检测:CheckForApplicationUpdate 组件使用与实现原理全解析
【免费下载链接】react-adminA frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design项目地址: https://gitcode.com/gh_mirrors/re/react-admin
对于长时间保持浏览器标签页打开的 SPA 后台应用,用户往往会停留在旧版本页面上,无法及时感知新版本部署。react-admin 内置的CheckForApplicationUpdate组件正是为解决这一痛点而生:它定时抓取页面源码并计算哈希,一旦检测到应用代码发生变化,就弹窗提示用户刷新页面,从而让"常驻后台"的用户始终运行最新版本。读完本文,你将掌握该组件的接入方式、全部 Props 配置、国际化定制方法,并深入理解其底层哈希比对机制。
组件定位:为什么 SPA 需要主动检测更新
传统的多页面应用每次跳转都会向服务器请求最新 HTML,天然能拿到新版本资源;而基于 react-admin 构建的单页应用(SPA)在首次加载后,HTML 与 JS 包都缓存在浏览器中,后续路由切换只是在前端内存中完成。如果管理员一直不刷新页面,即便后端已经部署了新版本,他们看到的仍然是旧界面、旧逻辑,甚至可能与新版后端 API 产生兼容性问题。
CheckForApplicationUpdate组件通过定期抓取当前 URL 并对比响应内容哈希的方式检测更新:构建工具(如 Vite、Webpack)在发版后通常会更新 HTML 中引用的 bundle 链接,因此 HTML 内容本身即可作为"是否已发布新版本"的可靠信号。该检测原理在当前仓库的实现中有完整体现,见 useCheckForApplicationUpdate.ts:组件使用fetch获取 URL 内容,将其作为文本计算哈希,与上一次记录的哈希比较,不同即视为有新版本。
快速接入:在自定义 Layout 中启用
CheckForApplicationUpdate组件设计为挂载在自定义布局(Layout)中,自身不渲染任何 DOM,只负责检测与通知。官方脚手架 create-react-admin 生成的模板项目也默认集成了该组件,参考 Layout.tsx:
// in src/MyLayout.tsx import type { ReactNode } from 'react'; import { CheckForApplicationUpdate, Layout } from 'react-admin'; export const MyLayout = ({ children }: { children: ReactNode}) => ( <Layout> {children} <CheckForApplicationUpdate /> </Layout> ); // in src/App.tsx import { Admin, ListGuesser, Resource } from 'react-admin'; import { MyLayout } from './MyLayout'; export const App = () => ( <Admin layout={MyLayout}> <Resource name="posts" list={ListGuesser} /> </Admin> );将其放在children之后即可:检测逻辑由内部的useCheckForApplicationUpdateHook 驱动,组件本身返回null(见 CheckForApplicationUpdate.tsx)。
Props 详解
<CheckForApplicationUpdate>接受以下 Props:
| Prop | Required | Type | Default | 说明 |
|---|---|---|---|---|
interval | 可选 | number | 3600000(1 小时) | 两次检查之间的时间间隔,单位毫秒 |
disabled | 可选 | boolean | false(生产模式下才启用) | 是否禁用自动检查 |
notification | 可选 | ReactNode | <ApplicationUpdatedNotification /> | 检测到更新时展示给用户的通知 |
onNewVersionAvailable | 可选 | function | 检测到新版本时执行的回调 | |
url | 可选 | string | 当前 URL | 用于检测代码更新的下载地址 |
fetchOptions | 可选 | RequestInit \| undefined | undefined | 传给fetch的请求选项 |
需要留意的是,CheckForApplicationUpdateProps在类型层面继承自UseCheckForApplicationUpdateOptions(见 CheckForApplicationUpdate.tsx),因此上述 Props 与底层 Hook 的选项一一对应,你也可以脱离组件、直接使用useCheckForApplicationUpdateHook 自行编排检测逻辑。
interval:自定义检查频率
默认每 1 小时(3600000毫秒)检查一次。可通过interval传入毫秒数来调整:
// in src/MyLayout.tsx import type { ReactNode } from 'react'; import { CheckForApplicationUpdate, Layout } from 'react-admin'; const HALF_HOUR = 30 * 60 * 1000; export const MyLayout = ({ children }: { children: ReactNode}) => ( <Layout> {children} <CheckForApplicationUpdate interval={HALF_HOUR} /> </Layout> );从源码看,该值最终作为setInterval的延迟参数使用(见 useCheckForApplicationUpdate.ts),组件卸载时通过clearInterval清理定时器,避免内存泄漏。检查频率越高,越早感知新版发布,但也意味着更频繁的网络请求,建议根据更新频率与服务器负载权衡。
disabled:按环境控制开关
disabled默认值为false,但该默认行为有一个重要前提:仅在 production(生产)模式下才真正启用检测。底层实现中disabled的默认值实际为process.env.NODE_ENV !== 'production'(见 useCheckForApplicationUpdate.ts),即开发环境下自动跳过检测,避免本地开发时频繁误报。
你还可以动态控制该开关,例如手动跟随环境变量:
// in src/MyLayout.tsx import type { ReactNode } from 'react'; import { CheckForApplicationUpdate, Layout } from 'react-admin'; export const MyLayout = ({ children }: { children: ReactNode}) => ( <Layout> {children} <CheckForApplicationUpdate disabled={process.env.NODE_ENV !== 'production'} /> </Layout> );notification:自定义更新提示 UI
默认通知由ApplicationUpdatedNotification组件渲染——一个带severity="info"的 MUIAlert,内含一个点击后调用window.location.reload()的按钮(见 ApplicationUpdatedNotification.tsx)。如果你希望用自己设计的通知样式替代默认的 Alert,可通过notification传入自定义元素,注意必须使用forwardRef包装你的组件,因为底层会通过useNotify将该元素注入通知系统:
// in src/MyLayout.tsx import { forwardRef, ReactNode } from 'react'; import { Layout, CheckForApplicationUpdate } from 'react-admin'; const CustomAppUpdatedNotification = forwardRef((props, ref) => ( <Alert ref={ref} severity="info" action={ <Button color="inherit" size="small" onClick={() => window.location.reload()} > Update </Button> } > A new version of the application is available. Please update. </Alert> )); const MyLayout = ({ children }: { children: ReactNode}) => ( <Layout> {children} <CheckForApplicationUpdate notification={<CustomAppUpdatedNotification />}/> </Layout> );如果只想调整文案(含按钮文字)而不重写整体 UI,可参考下文"国际化"一节;如果需要完全接管"检测到新版本"之后的业务行为,则应使用onNewVersionAvailable。
onNewVersionAvailable:接管新版本处理逻辑
高级用户可以不显示默认通知,而是在检测到新版本时执行自定义逻辑,例如在刷新前把用户偏好备份到localStorage:
import { CheckForApplicationUpdate, useNotify } from "react-admin"; export const MyCheckForApplicationUpdate = () => { const notify = useNotify(); const onNewVersionAvailable = () => { // Perform a backup of user preference in localStorage in case bad things happen const preference1 = localStorage.getItem("preference1"); const preference2 = localStorage.getItem("preference2"); const checkpointData = { preference1, preference2, }; localStorage.setItem( `checkpoint_${new Date().toISOString()}`, JSON.stringify(checkpointData), ); // Notify user notify("New Version Ready to Update"); }; return ( <CheckForApplicationUpdate onNewVersionAvailable={onNewVersionAvailable} /> ); };从实现看,当未传入onNewVersionAvailable时,组件内部会构造一个默认回调:通过useNotify弹出type: 'info'、autoHideDuration: null(即不自动消失)的通知,通知内容正是notificationProp(见 CheckForApplicationUpdate.tsx)。而一旦你传入自定义回调,useCheckForApplicationUpdate会通过useEvent稳定地持有它,并在每次检测到哈希变化时调用(见 useCheckForApplicationUpdate.ts)。
url:指定检测目标地址
默认抓取当前页面 URL 作为检测源。如果你的应用部署在 CDN 或多域名环境下,可显式指定根地址,确保每次抓取到的是同一份 HTML:
// in src/MyLayout.tsx import type { ReactNode } from 'react'; import { CheckForApplicationUpdate, Layout } from 'react-admin'; const MY_APP_ROOT_URL = 'https://admin.mycompany.com'; export const MyLayout = ({ children }: { children: ReactNode}) => ( <Layout> {children} <CheckForApplicationUpdate url={MY_APP_ROOT_URL} /> </Layout> );fetchOptions:透传 fetch 请求选项
检测本质上是一次fetch请求,你可以通过fetchOptions传入标准的 RequestInit 选项,例如自定义请求头、超时或缓存策略。官方文档特别提示:视服务器端 HTTP 缓存配置而定,建议设置{ cache: "no-cache" },确保每次都向源站发起校验、拿到真实的最新内容,而不是命中浏览器或中间层缓存导致误判。
从源码看,fetchOptions会同时影响初次哈希采集与定时轮询两次请求(见 useCheckForApplicationUpdate.ts),因此断网等场景不会触发误报或异常。
国际化:定制默认通知文案
默认通知的所有文字均可通过 react-admin 的 i18n 机制覆盖,涉及两个翻译键:
ra.notification.application_update_available:通知正文文本ra.action.update_application:重新加载按钮文本
在英文语言包 ra-language-english 中默认值为'A new version is available.'与'Reload Application';法语语言包 ra-language-french 中则为'Une mise à jour est disponible.'与"Recharger l'application"。在你的自定义翻译资源中覆盖这两个键即可,例如:
// in src/i18n/zh.ts const zhMessages = { ra: { notification: { application_update_available: '检测到新版本,点击下方按钮刷新页面。', }, action: { update_application: '立即刷新', }, }, };同时,ApplicationUpdatedNotification.tsx 还暴露了notificationText、updateText与ButtonProps三个 props,允许在不重写整个组件的前提下直接注入文本与按钮属性,适合需要更精细定制又不想维护全套自定义通知的场景。
源码级原理:哈希比对与防重复通知
深入 useCheckForApplicationUpdate.ts 可以梳理出完整的检测链路:
- 首次采集基线哈希:挂载后立即
getHashForUrl(url, fetchOptions)抓取页面文本并计算哈希,存入currentHashref(L30-L39); - 定时轮询比对:以
interval为周期反复抓取,一旦发现新哈希与currentHash不同,先更新currentHash再调用onNewVersionAvailable()(L44-L57)。先更新再回调的设计很关键——它确保"通知被用户关闭或未处理"后,不会因同一版本反复触发多次提示; - 错误静默处理:请求失败或响应异常时返回
null,.catch吞掉异常,避免网络抖动打断后台检测(L54-L56、L70-L79); - 哈希算法:采用 cyrb53 风格的轻量字符串哈希(
hash函数,L82-L96),基于Math.imul实现,计算成本极低,适合对整段 HTML 文本频繁求值。该哈希仅用于版本对比而非密码学用途,碰撞概率在检测场景下可以接受。
整个 Hook 与组件位于两个层面:useCheckForApplicationUpdate作为纯逻辑 Hook 放在 ra-core(packages/ra-core/src/util/useCheckForApplicationUpdate.ts),并在 util/index.ts 统一导出;CheckForApplicationUpdate组件与默认通知 UI 放在 ra-ui-materialui(packages/ra-ui-materialui/src/layout/CheckForApplicationUpdate.tsx、ApplicationUpdatedNotification.tsx),并从 layout/index.ts 对外导出。这种"逻辑与 UI 分离"的架构意味着:即使不使用 Material UI,也可以仅依赖 ra-core 的 Hook 自建更新检测与提示界面。
使用注意事项
- 仅生产环境默认启用:开发模式下检测被默认禁用(
disabled = process.env.NODE_ENV !== 'production'),需要在生产构建中才能观察到效果; - 缓存策略影响准确性:若服务器对 HTML 启用了强缓存,请通过
fetchOptions: { cache: "no-cache" }绕过,否则可能检测不到已发布的新版本; - URL 一致性:自定义
url时确保它与实际部署入口指向同一份 HTML,避免因 URL 差异造成哈希持续不同而频繁误报; - SPA 适配:该方案依赖"构建产物更新后 HTML 中的 bundle 链接发生变化"这一前提,对使用内容哈希命名的现代打包工具(Vite、Webpack 等)适用;若你的部署方式始终保持 HTML 不变(如内联全部资源),则哈希比对将失效,需要改用其他更新信号。
至此,你已完整掌握CheckForApplicationUpdate的接入、配置、定制与底层原理:它用一次简单的fetch+ 哈希比对,为常驻后台的 SPA 用户补上了"主动感知新版本"的关键一环。
【免费下载链接】react-adminA frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design项目地址: https://gitcode.com/gh_mirrors/re/react-admin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考