React Router v7 已发布。 查看文档
网络并发管理
本页内容

网络并发管理

在构建 Web 应用程序时,管理网络请求可能是一项艰巨的任务。确保最新数据和处理并发请求的挑战通常导致应用程序中出现复杂的逻辑来处理中断和竞态条件。Remix 通过自动化网络管理,镜像并扩展 Web 浏览器的直观行为,简化了此过程。

为了帮助理解 Remix 的工作原理,请记住全栈数据流中,在 form 提交后,Remix 将从加载器中获取新的数据。这称为重新验证。

与浏览器行为的自然对齐

Remix 对网络并发的处理深受 Web 浏览器在处理文档时的默认行为的启发

  • 浏览器链接导航:当您在浏览器中点击一个链接,然后在页面转换完成之前点击另一个链接时,浏览器会优先处理最新的 action。它会取消初始请求,只关注最后点击的链接。

    • Remix 的方法:Remix 以相同的方式管理客户端导航。当在 Remix 应用程序中点击链接时,它会为与目标 URL 关联的每个加载器启动获取请求。如果另一个导航中断了初始导航,Remix 会取消之前的获取请求,确保只有最新的请求继续进行。
  • 浏览器表单提交:如果您在浏览器中发起一个表单提交,然后快速再次提交另一个表单,浏览器会忽略第一个提交,只处理最新的提交。

    • Remix 的方法:Remix 在处理表单时会模仿这种行为。如果提交了一个表单,并且在第一个表单完成之前发生了另一次提交,Remix 会取消原始的获取请求。然后,它会等待最新的提交完成后再触发页面重新验证。

并发提交和重新验证

虽然标准浏览器在导航和表单提交时每次只能处理一个请求,但 Remix 提升了这种行为。与导航不同,使用 useFetcher 可以同时进行多个请求。

Remix 旨在高效地处理对服务器 action 的多个表单提交和并发重新验证请求。它确保一旦有新数据可用,状态会立即更新。但是,Remix 还会防止潜在的陷阱,避免在其他 action 引入竞态条件时提交过时的数据。

例如,如果有三个表单提交正在进行中,其中一个完成了,Remix 会立即使用该数据更新 UI,而无需等待其他两个,以确保 UI 保持响应性和动态。随着剩余提交的完成,Remix 会继续更新 UI,确保显示最新的数据。

为了帮助理解一些可视化图表,下面是图表中使用的符号的图例:

  • |: 提交开始
  • ✓: 操作完成,数据重新验证开始
  • ✅: 重新验证的数据已提交到 UI
  • ❌: 请求已取消
submission 1: |----✓-----✅
submission 2:    |-----✓-----✅
submission 3:             |-----✓-----✅

然而,如果后续提交的重新验证比之前的提交更早完成,Remix 会丢弃较早的数据,确保 UI 中只反映最新的信息。

submission 1: |----✓---------❌
submission 2:    |-----✓-----✅
submission 3:             |-----✓-----✅

因为提交 (2) 的重新验证开始得较晚,但比提交 (1) 更早完成,所以提交 (1) 的请求会被取消,只有提交 (2) 的数据会被提交到 UI。因为它请求得较晚,所以更有可能包含 (1) 和 (2) 的更新值。

可能存在过时数据的风险

您的用户不太可能遇到这种情况,但在基础设施不一致的极少数情况下,用户仍然有可能看到过时的数据。即使 Remix 取消了过时数据的请求,它们最终仍然会到达服务器。在浏览器中取消请求只是释放了该请求的浏览器资源,它无法“赶上”并阻止其到达服务器。在极少数情况下,已取消的请求可能会在中断的 action 的重新验证完成后更改数据。请考虑以下图表:

     👇 interruption with new submission
|----❌----------------------✓
       |-------✓-----✅
                             👆
                  initial request reaches the server
                  after the interrupting submission
                  has completed revalidation

用户现在看到的数据与服务器上的数据不同。请注意,这个问题非常罕见,并且也存在于默认的浏览器行为中。初始请求到达服务器的时间晚于第二次提交和重新验证的时间,这在任何网络和服务器基础设施上都是意料之外的。如果这是您基础设施中的一个问题,您可以使用表单提交发送时间戳,并编写服务器逻辑来忽略过时的提交。

示例

在诸如组合框之类的 UI 组件中,每次击键都可能触发一个网络请求。管理如此快速、连续的请求可能很棘手,尤其是在确保显示的结果与最新的查询匹配时。然而,借助 Remix,此挑战会自动处理,确保用户看到正确的结果,而无需开发人员微观管理网络。

import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno

export async function loader({
  request,
}: LoaderFunctionArgs) {
  const { searchParams } = new URL(request.url);
  const cities = await searchCities(searchParams.get("q"));
  return json(cities);
}

export function CitySearchCombobox() {
  const fetcher = useFetcher<typeof loader>();

  return (
    <fetcher.Form action="/city-search">
      <Combobox aria-label="Cities">
        <ComboboxInput
          name="q"
          onChange={(event) =>
            // submit the form onChange to get the list of cities
            fetcher.submit(event.target.form)
          }
        />

        {/* render with the loader's data */}
        {fetcher.data ? (
          <ComboboxPopover className="shadow-popup">
            {fetcher.data.length > 0 ? (
              <ComboboxList>
                {fetcher.data.map((city) => (
                  <ComboboxOption
                    key={city.id}
                    value={city.name}
                  />
                ))}
              </ComboboxList>
            ) : (
              <span>No results found</span>
            )}
          </ComboboxPopover>
        ) : null}
      </Combobox>
    </fetcher.Form>
  );
}

应用程序只需要知道如何查询数据以及如何渲染它,Remix 会处理网络。

结论

Remix 为开发人员提供了一种直观的、基于浏览器的方法来管理网络请求。通过镜像浏览器行为并在需要时进行增强,它简化了并发、重新验证和潜在竞争条件的复杂性。无论您是构建简单的网页还是复杂的 Web 应用程序,Remix 都能确保您的用户交互流畅、可靠且始终保持最新。

文档和示例基于 MIT 许可 MIT