竞态条件

竞态条件

虽然无法消除应用程序中每一种可能的竞态条件,但 React Router 会自动处理 Web 用户界面中最常见的竞态条件。

浏览器行为

React Router 处理网络并发的方式深受 Web 浏览器处理文档行为的启发。

考虑点击一个链接到一个新文档,然后在新页面完成加载之前点击另一个不同的链接。浏览器将

  1. 取消第一个请求
  2. 立即处理新的导航

相同的行为适用于表单提交。当一个待处理的表单提交被新的提交中断时,第一个会被取消,新的提交会被立即处理。

React Router 行为

像浏览器一样,通过链接和表单提交中断的导航将取消正在进行的数据请求,并立即处理新事件。

Fetchers 有些细微差别,因为它们不像导航那样是单例事件。Fetchers 不能中断其他 fetcher 实例,但它们可以中断自身,并且行为与其他所有情况相同:取消被中断的请求并立即处理新请求。

然而,在重新验证方面,Fetchers 确实会相互交互。在 fetcher 的 action 请求返回到浏览器后,会发送所有页面数据的重新验证。这意味着可能会同时存在多个正在进行的重新验证请求。React Router 将提交所有“新鲜”的重新验证响应,并取消任何过时的请求。过时的请求是指任何开始时间早于已返回的请求。

这种网络管理方式可以防止由网络竞态条件引起的最常见的 UI 错误。

由于网络是不可预测的,并且您的服务器仍然会处理这些取消的请求,因此您的后端仍然可能遇到竞态条件并存在潜在的数据完整性问题。这些风险与使用带有纯 HTML <forms> 的默认浏览器行为的风险相同,我们认为这些风险很低,并且超出 React Router 的范围。

实际好处

考虑构建一个自动完成组合框。当用户输入时,您向服务器发送请求。当他们输入每个新字符时,您发送一个新的请求。重要的是不要向用户显示不再在文本字段中的值的结果。

当使用 fetcher 时,这是自动为您管理的。考虑以下伪代码

// route("/city-search", "./search-cities.ts")
export async function loader({ request }) {
  const { searchParams } = new URL(request.url);
  return searchCities(searchParams.get("q"));
}
export function CitySearchCombobox() {
  const fetcher = useFetcher();

  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)
          }
        />

        {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>
  );
}

调用 fetcher.submit 将自动取消该 fetcher 上的待处理请求。这确保您永远不会向用户显示针对不同输入值的请求的结果。

文档和示例 CC 4.0