React 的渲染缓存

Avatar of Atishay Jain
Atishay Jain

DigitalOcean 提供适合您旅程各个阶段的云产品。立即开始使用 $200 免费信用额度!

服务器端渲染 (SSR) 是一种非常有用的技术,可以使 Web 应用程序看起来更快。在 JavaScript 解析之前显示初始 HTML,并且当用户决定点击什么时,我们的处理程序已经准备就绪。

React 中的服务器端渲染需要额外的设置工作,并产生服务器成本。此外,如果您的服务器团队无法在您的服务器上运行 JavaScript,您就会陷入困境。它会极大地复杂化 CDN 设置,尤其是当您有需要登录的页面以及用户的信息需要管理时。

我想介绍一个名为渲染缓存的新概念。这是一个很酷的技巧,它可以为用户提供与 SSR 相似的即时性能提升,而无需诉诸在服务器上编写代码。

什么是渲染缓存?

从静态 HTML 页面迁移到单页面应用程序 (SPA) 使 Web 传统上依赖的整个缓存概念出现了巨大漏洞。虽然浏览器优化了初始 HTML 的传递和渲染,但 SPA 会将它们留空,以便稍后填充。

渲染缓存优化 SPA 渲染,可以显着提高 Web 页面的感知加载时间。它通过在浏览器中缓存渲染的 HTML 来实现这一点,以便在下次加载时提供该显示,而无需执行会占用显示时间的 JavaScript 解析。

启用渲染缓存

我们之前提到,为 React 设置 SSR 需要额外的设置和服务器成本。渲染缓存避免了这些负担。

设置它需要几个步骤。让我们将其分解成易于理解的部分。

步骤 1:确定正确的缓存状态

确定当前页面在用户下次访问时渲染相同的条件。

例如,您可以创建一个包含当前构建编号或用户 ID 的 JSON 对象。关键是确保状态封装在 URL、本地存储或 Cookie 中,并且不需要服务器调用。

步骤 2:设置 API 调用

确保所有 API 调用都发生在渲染 React 的调用之前。这在常规用例中也是有意义的,因为我们希望防止页面在用户下方发生更改,从而导致闪烁。

步骤 3:在卸载处理程序中本地缓存

现在向文档添加卸载事件处理程序。将当前 DOM 存储在 localStorage/indexDB 中。

这看起来像这样,使用构建编号和用户 ID 来确定步骤 1 中介绍的缓存状态

window.addEventListener("beforeunload", () => {
  // Production code would also be considerate of localStorage size limitations
  // and would do a LRU cache eviction to maintain sanity on storage.
  // There should also be a way to clear this data when the user signs out
  window.localStorage.setItem(
    `lastKnown_${window.location.href}`,
    JSON.stringify({
      conditions: {
        userId: "<User ID>",
        buildNo: "<Build No.>"
      },
      data: document.getElementById("content").innerHTML
    })
  );
});

// If you want to store data per user, you can add user ID to the key instead of the condition.

步骤 4:在加载时恢复最后已知的状态

接下来,我们希望从浏览器的本地存储中提取最后已知的状态,以便我们可以在以后的访问中使用它。我们通过在 HTML 文件(例如在文档的 the body 标记下的 index.html)中添加以下内容来实现此目的。

<!-- ... -->
</body>

<script>
  let lastKnownState = window.localStorage.getItem(`lastKnown_${window.location.href}`);
  
  lastKnownState = lastKnownState && JSON.parse(lastKnownState);
  
  if (lastKnownState &&
    lastKnownState.conditions.userId === "<User ID>" &&
    lastKnownState.conditions.buildNo === "<Build No.>") {
    document.getElementById('content').innerHTML = lastKnownState.data;
    window.hasRestoredState = true;
  }
</script>

步骤 5:在 React 中渲染最后已知的状态

这是重点所在。现在,我们已经将用户的最后已知状态在 DOM 中可见,我们可以通过使用 hydrate 有条件地更新 React 渲染的顶层来获取完整内容并以该状态渲染我们的应用程序。事件处理程序将在此代码命中后变为功能性,但 DOM 不应更改。

import {render, hydrate} from "react-dom"

if (window.hasRestoredState) {
  hydrate(<MyPage />, document.getElementById('content'));
} else {
  render(<MyPage />, document.getElementById('content'));
}

步骤 6:始终异步

将您的脚本标记从 sync 更改为 async/defer 以加载 JavaScript 文件。这是确保前端平滑加载和渲染体验的另一个关键步骤。

就是这样!重新加载页面以查看性能提升。

衡量改进

好的,您做了所有这些工作,现在您想知道您的网站的性能如何。您需要对改进进行基准测试。

渲染缓存在您在知道要渲染的内容之前需要进行多次服务器调用时表现出色。在脚本密集型页面上,JavaScript 实际上可能需要很长时间才能解析。

您可以在 Chrome 的 DevTools 中的性能选项卡中衡量加载性能。

在 Chrome 的 DevTools 的性能选项卡中衡量渲染

理想情况下,您将使用来宾配置文件,以便您的浏览器扩展不会干扰测量。您应该在重新加载时看到显着的改进。在上面的屏幕截图中,我们有一个示例应用程序,其中包含一个异步 data.json 获取调用,该调用在调用 ReactDOM.hydrate 之前执行。使用渲染缓存,渲染甚至在数据加载之前就完成了!

总结

渲染缓存是一种巧妙的技术,通过向最终 HTML 添加缓存层并将其显示给用户,以确保相同 Web 页面的重新获取的感知速度更快。经常访问您网站的用户将是受益最大的用户。

如您所见,我们使用很少的代码就实现了这一点,而我们获得的性能提升是巨大的。请在您的网站上尝试一下,并发表您的评论。我很想知道您的网站性能是否也获得了与我所体验到的相同的显着提升。