竞争条件

竞争条件

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

浏览器行为

React Router 处理网络并发的方式很大程度上受到 Web 浏览器处理文档行为的启发。

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

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

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

React Router 行为

与浏览器类似,使用链接和表单提交的中断导航将取消正在进行的数据请求,并立即处理新的事件。

Fetcher 比导航更复杂一些,因为它们不是像导航那样的单例事件。Fetcher 无法中断其他 Fetcher 实例,但它们可以中断自身,并且行为与其他所有情况相同:取消被中断的请求并立即处理新的请求。

然而,Fetcher 在重新验证方面确实会相互影响。Fetcher 的操作请求返回到浏览器后,会发送所有页面数据的重新验证请求。这意味着多个重新验证请求可以同时处于飞行状态。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