发布于 2026-01-05 10 阅读
0

React 应用的缓存清除

React 应用的缓存清除

简而言之,对你的应用使用语义化版本控制SemVer)meta.json ,并在每次构建时生成一个不会被浏览器缓存的文件。当版本不匹配时,使缓存失效并强制重新加载应用。

注:本文中的示例和解释均基于 React,但该策略适用于任何 Web 应用程序/框架。

缓存固然好用,但缓存失效一直是个难题。清除浏览器中加载的 Web 应用的缓存已经够难了清除保存到主屏幕的 Web 应用的缓存更难

缓存简介 —

服务器缓存: Web 服务器会在用户首次请求资源时将其缓存。从第二次请求开始,资源将从服务器缓存中直接提供。这其中还有很多细节——例如 CDN、源服务器、边缘服务器等等,但我们这里就不一一赘述了。由于我们可以控制服务器,因此清除服务器缓存非常简单,每次部署新版本时,我们都可以自动或手动清除旧缓存。

浏览器缓存:浏览器​​也会以自己的方式缓存资源。当用户首次在浏览器中加载网站时,浏览器会将一些资源(通常是图片、JS 和 CSS 等资源)缓存到本地。下次用户访问同一网站时,浏览器会直接从本地缓存中提供资源。由于我们无法控制用户的浏览器,因此清除浏览器缓存一直以来都比较麻烦。虽然有了缓存头以及像 webpack 这样的构建工具会在每次构建时生成唯一的代码块,缓存管理变得稍微容易了一些,但仍然存在一些问题。

以下是浏览器缓存的一些陷阱——

  1. 如果网站在同一标签页中刷新,浏览器有时会忽略缓存验证——如果用户固定了标签页,即使服务器缓存被清除,网站也很有可能从浏览器缓存加载。
  2. 如果您的应用注册了Service Worker ,那么只有当用户在新标签页中打开网站时,Service Worker缓存才会失效。如果标签页从未关闭,用户将永远被 Service Worker 缓存所困。
  3. 如果用户将网站添加到手机/平板电脑的主屏幕,那么只有当用户主动退出应用时,浏览器缓存才会失效——这几乎和在浏览器中一直打开同一个标签页一样。我知道有些人几个月都不退出主屏幕应用。

理想情况下,缓存有助于加快网站加载速度。禁用缓存并非解决之道,而且这种方法也不可靠,因为你无法控制用户浏览器的行为。我们希望找到一种方法,在每次将新版本的应用程序部署到服务器时,都能清除浏览器或 Service Worker 的缓存。

一种简单而有效的方法

  • SemVer你的部署
  • 将应用版本打包到应用中
  • meta.json每次构建时生成一个包含应用程序版本的文件
  • meta.json加载时获取并比较版本
  • 版本不匹配时强制清除缓存并重新加载

SemVer你的部署

使用SemVer对所有部署进行版本控制。我个人使用以下三个 npm 命令,它们会自动递增包版本,并创建一个包含相应版本标签的 Git 提交。

  • npm version patch仅包含错误修复的版本
  • npm version minor适用于包含新功能(无论是否修复错误)的版本
  • npm version major适用于重大版本发布或重大功能更新

记得推送你的提交时要添加--tag属性——git push origin master --tags

将应用版本打包到应用中

在 webpack 构建期间(或相关构建工具)解析软件包版本,并在应用程序中设置一个全局变量,以便您可以方便地在浏览器控制台中检查版本,并使用该变量与最新版本进行比较。

import packageJson from '{root-dir}/package.json';
global.appVersion = packageJson.version;
Enter fullscreen mode Exit fullscreen mode

设置完成后,您可以通过在浏览器控制台中输入来检查应用程序版本appVersion

meta.json每次构建时生成一个包含应用程序版本的文件

运行脚本,meta.jsonpublic应用程序目录中生成一个文件。

添加一个prebuildnpm 脚本,该脚本将meta.json在每次执行之前生成该文件build

/* package.json */

{
    "scripts": {
        "generate-build-version": "node generate-build-version",
        "prebuild": "npm run generate-build-version",
        // other scripts
     }
}
Enter fullscreen mode Exit fullscreen mode
/* generate-build-version.js */

const fs = require('fs');
const packageJson = require('./package.json');

const appVersion = packageJson.version;

const jsonData = {
  version: appVersion
};

var jsonContent = JSON.stringify(jsonData);

fs.writeFile('./public/meta.json', jsonContent, 'utf8', function(err) {
  if (err) {
    console.log('An error occured while writing JSON Object to meta.json');
    return console.log(err);
  }

  console.log('meta.json file has been saved with latest version number');
});
Enter fullscreen mode Exit fullscreen mode

每次构建完成后,部署应用后,meta.json即可通过路径访问该/meta.json文件,并像使用 REST 端点一样获取 JSON 数据。由于浏览器不会缓存 XHR 请求,因此该文件不会被浏览器缓存。meta.json即使您的 bundle 文件被缓存,您也始终能获取到最新文件。

因此,如果appVersion您的 bundle 文件中的值小于 中的值versionmeta.json那么我们就知道浏览器缓存已过期,需要使其失效

你可以使用此脚本来比较语义版本——

// version from `meta.json` - first param
// version in bundle file - second param
const semverGreaterThan = (versionA, versionB) => {
  const versionsA = versionA.split(/\./g);

  const versionsB = versionB.split(/\./g);
  while (versionsA.length || versionsB.length) {
    const a = Number(versionsA.shift());

    const b = Number(versionsB.shift());
    // eslint-disable-next-line no-continue
    if (a === b) continue;
    // eslint-disable-next-line no-restricted-globals
    return a > b || isNaN(b);
  }
  return false;
};
Enter fullscreen mode Exit fullscreen mode

你也可以在我的GitHub 示例中找到这段代码。

meta.json加载时获取并比较版本

App挂载完成后,获取meta.json并比较当前版本与服务器上的最新版本。

当版本不匹配=> 强制清除缓存并重新加载;
当版本相同时 => 渲染应用程序的其余部分。

我开发了一个CacheBuster组件,可以强制清除缓存并重新加载网站。该逻辑适用于大多数网站,但也可以根据具体应用场景进行调整。

/* CacheBuster component */
import packageJson from '../package.json';
global.appVersion = packageJson.version;

const semverGreaterThan = (versionA, versionB) => {
    // code from above snippet goes here
}

export default class CacheBuster extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: true,
      isLatestVersion: false,
      refreshCacheAndReload: () => {
        console.log('Clearing cache and hard reloading...')
        if (caches) {
          // Service worker cache should be cleared with caches.delete()
          caches.keys().then(function(names) {
            for (let name of names) caches.delete(name);
          });
        }
        // delete browser cache and hard reload
        window.location.reload(true);
      }
    };
  }

  componentDidMount() {
    fetch('/meta.json')
      .then((response) => response.json())
      .then((meta) => {
        const latestVersion = meta.version;
        const currentVersion = global.appVersion;

        const shouldForceRefresh = semverGreaterThan(latestVersion, currentVersion);
        if (shouldForceRefresh) {
          console.log(`We have a new version - ${latestVersion}. Should force refresh`);
          this.setState({ loading: false, isLatestVersion: false });
        } else {
          console.log(`You already have the latest version - ${latestVersion}. No cache refresh needed.`);
          this.setState({ loading: false, isLatestVersion: true });
        }
      });
  }

  render() {
    const { loading, isLatestVersion, refreshCacheAndReload } = this.state;
    return this.props.children({ loading, isLatestVersion, refreshCacheAndReload });
  }
}
Enter fullscreen mode Exit fullscreen mode

我们可以使用这个组件来控制组件CacheBuster中的渲染。App

/* App component */
class App extends Component {
  render() {
    return (
      <CacheBuster>
        {({ loading, isLatestVersion, refreshCacheAndReload }) => {
          if (loading) return null;
          if (!loading && !isLatestVersion) {
            // You can decide how and when you want to force reload
            refreshCacheAndReload();
          }

          return (
            <div className="App">
              <header className="App-header">
                <h1>Cache Busting - Example</h1>
                <p>
                  Bundle version - <code>v{global.appVersion}</code>
                </p>
              </header>
            </div>
          );
        }}
      </CacheBuster>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

您也可以在这里找到这两个组件的代码——

CacheBuster - CacheBuster.js
应用 - App.js

版本不匹配时强制清除缓存并重新加载

每次应用加载时,我们都会检查最新版本。根据应用版本是否过时,我们可以采用不同的方式清除缓存。

例如,

  • 您可以在渲染应用程序之前进行强制重新加载。
  • 您可以显示一个模态框/弹出窗口,要求用户点击按钮并触发强制重新加载。
  • 应用处于空闲状态时,您可以强制重启。
  • 几秒钟后你可以强制重新加载。setTimeout()

你可以在这个仓库中找到本文中的完整代码以及一个可运行的示例——cache-busting-example

以上就是全部内容。如果您对这种方法有任何反馈(好的或不好的),请在评论区告诉我。

寻宝真有趣!🎉

文章来源:https://dev.to/flexdinesh/cache-busting-a-react-app-22lk