在构建 Web 应用程序时,管理网络请求可能是一项艰巨的任务。确保数据最新和处理并发请求的挑战通常会导致应用程序中出现复杂的逻辑来处理中断和竞态条件。React Router 通过自动化网络管理,同时模仿并扩展 Web 浏览器的直观行为,简化了这一过程。
为了帮助理解 React Router 如何处理并发,重要的是要记住,在 form
提交后,React Router 将从 loader
中获取新数据。这被称为重新验证。
React Router 对网络并发的处理在很大程度上受到了 Web 浏览器处理文档时默认行为的启发。
浏览器行为:当您在浏览器中点击一个链接,然后在页面转换完成之前又点击了另一个链接时,浏览器会优先处理最近的操作
。它会取消初始请求,只关注最新点击的链接。
React Router 行为:React Router 以相同的方式管理客户端导航。在 React Router 应用程序中点击链接时,它会为目标 URL 关联的每个 loader
发起获取请求。如果另一个导航中断了初始导航,React Router 会取消之前的获取请求,确保只有最新的请求继续进行。
浏览器行为:如果您在浏览器中发起一次表单提交,然后迅速再次提交另一个表单,浏览器会忽略第一次提交,只处理最新的提交。
React Router 行为:React Router 在处理表单时模仿了这种行为。如果一个表单被提交,而在第一次提交完成前又发生了另一次提交,React Router 会取消原始的获取请求。然后它会等待最新的提交完成后再触发页面重新验证。
虽然标准浏览器在导航和表单提交方面每次只能处理一个请求,但 React Router 提升了这种行为。与导航不同,使用 useFetcher
可以同时处理多个进行中的请求。
React Router 旨在高效地处理对服务端 action
的多个表单提交和并发的重新验证请求。它确保一旦有新数据可用,状态就会立即更新。然而,React Router 也通过在其他 action
引入竞态条件时不提交过时数据来防范潜在的陷阱。
例如,如果有三个表单提交正在进行中,其中一个完成后,React Router 会立即用该数据更新 UI,而无需等待其他两个,从而使 UI 保持响应和动态。随着剩余的提交完成,React Router 会继续更新 UI,确保显示的是最新的数据。
使用此图例
|
: 提交开始我们可以在下图中将此场景可视化
submission 1: |----✓-----✅
submission 2: |-----✓-----✅
submission 3: |-----✓-----✅
然而,如果后续提交的重新验证在较早的提交之前完成,React Router 会丢弃较早的数据,确保 UI 中只反映最新的信息。
submission 1: |----✓---------❌
submission 2: |-----✓-----✅
submission 3: |-----✓-----✅
因为提交 (2) 的重新验证开始得更晚,但比提交 (1) 更早完成,所以提交 (1) 的请求被取消,只有来自提交 (2) 的数据被提交到 UI。由于它是后来请求的,所以它更有可能包含来自 (1) 和 (2) 的更新值。
您的用户不太可能遇到这种情况,但在基础设施不一致的极少数情况下,用户仍有可能看到过时的数据。尽管 React Router 会取消对过时数据的请求,但这些请求最终仍会到达服务器。在浏览器中取消请求只是释放该请求的浏览器资源;它无法“追上”并阻止请求到达服务器。在极其罕见的情况下,一个被取消的请求可能会在中断的 action
的重新验证完成后更改数据。请看这个示意图
👇 interruption with new submission
|----❌----------------------✓
|-------✓-----✅
👆
initial request reaches the server
after the interrupting submission
has completed revalidation
用户现在看到的数据与服务器上的数据不同。请注意,这个问题既极其罕见,也存在于浏览器的默认行为中。初始请求比第二次的提交和重新验证都晚到达服务器,这在任何网络和服务器基础设施上都是意想不到的。如果您的基础设施存在此问题,您可以在表单提交时发送时间戳,并编写服务器逻辑来忽略过时的提交。
在像组合框这样的 UI 组件中,每次按键都可能触发网络请求。管理这种快速、连续的请求可能很棘手,尤其是在确保显示结果与最新查询匹配时。然而,有了 React Router,这个挑战会自动得到处理,确保用户看到正确的结果,而开发人员无需微观管理网络。
export async function loader({ request }) {
const { searchParams } = new URL(request.url);
const cities = await searchCities(searchParams.get("q"));
return 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>
);
}
应用程序需要知道的只是如何查询数据以及如何渲染它。React Router 负责处理网络。
React Router 为开发人员提供了一种直观、基于浏览器的方法来管理网络请求。通过模仿浏览器行为并在需要时对其进行增强,它简化了并发、重新验证和潜在竞态条件的复杂性。无论您是构建简单的网页还是复杂的 Web 应用程序,React Router 都能确保您的用户交互流畅、可靠且始终保持最新。