虽然不可能完全消除应用程序中的所有竞态条件,但 React Router 会自动处理 Web 用户界面中最常见的竞态条件。
React Router 处理网络并发的方式深受 Web 浏览器处理文档时行为的启发。
设想你点击了一个链接,指向一个新文档,然后在新页面加载完成之前又点击了另一个不同的链接。浏览器会
同样的行为也适用于表单提交。当一个挂起的表单提交被一个新的提交中断时,第一个提交会被取消,新的提交会立即被处理。
与浏览器类似,通过链接和表单提交中断的导航会取消正在进行的(in flight)数据请求,并立即处理新的事件。
Fetcher 有点更微妙,因为它们不像导航那样是单例事件。Fetcher 不能中断其他 fetcher 实例,但它们可以中断自己,并且行为与其他情况相同:取消被中断的请求,立即处理新的请求。
然而,Fetcher 在重新验证时会相互作用。当 fetcher 的 action 请求返回到浏览器后,会发送一个针对所有页面数据的重新验证。这意味着多个重新验证请求可以同时进行中(in-flight)。React Router 会提交所有“新鲜”的重新验证响应,并取消任何过时的请求。过时的请求是指任何开始时间早于已返回的请求。
这种网络管理可以防止由网络竞态条件引起的常见 UI 错误。
由于网络不可预测,并且你的服务器仍在处理这些已取消的请求,你的后端仍可能遇到竞态条件并存在潜在的数据完整性问题。这些风险与使用纯 HTML <forms>
的默认浏览器行为时的风险相同,我们认为这些风险较低,并且超出 React Router 的范围。
考虑构建一个预输入(type-ahead)组合框。当用户输入时,你向服务器发送请求。当他们每输入一个新字符时,你发送一个新的请求。重要的是不要向用户显示不再存在于文本字段中的值的结果。
使用 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 上挂起的请求。这确保你不会向用户显示与当前输入值不同的请求结果。