React Router Logo
2022 年 11 月 2 日

React 路由 Remix

Matt Brophy
员工开发人员

今年早些时候,我们开始着手 Remix React 路由,旨在将所有 Remix 数据 API(loadersactionsfetchers 等)迁移到 React Router。随着最近发布的 React Router v6.4.0,我们很自豪地宣布我们已经完成了这项工作......并且我们认为我们做得更好 😃。我们不仅修复了一些边缘情况的错误,还稳定了一些 API 并引入了一些非常棒的新 API。以下是更改的快速概述,但我们鼓励您查看博客文章以获取更多信息。

  • 🆕 使用 useRevalidator 以编程方式重新验证
  • 🆕 使用 defer/Await 将您的关键/非关键数据分离
  • 🆕 使用 useRouteLoaderData 获取特定路由的加载器数据
  • unstable_shouldReload 已稳定为 shouldRevalidate
  • 🔥 新的改进的 <ScrollRestoration getKey> 方法允许更精细地控制滚动恢复
  • 🔥 捕获和错误边界已合并为单个 errorElement
  • 🐞 fetcher.load 调用现在参与重新验证 - 正如他们应该一直以来的那样!

React 路由 Remix

现在我们可以反转脚本并开始 React 路由 Remix,以便我们可以将这些更改带回 Remix 用户(并在过程中删除 大量 Remix 代码)。对你们所有人的好消息是,我们计划以迭代方式进行,并且不会进行重大发布 🤯。我们认为我们正在使用的方法非常酷,所以我们想与大家分享。

您可以将 Remix 的架构视为具有 4 个主要方面

  1. 服务器导航 + 数据获取
  2. 服务器 HTML 渲染
  3. 客户端水合
  4. 客户端导航 + 数据获取

这 4 个部分也恰好是很好地解耦的,因此它们为我们提供了干净的边界,以迭代的方式来处理这个问题,以避免一次性的大爆炸发布。这应该意味着 Remix 用户将拥有更平滑的集成路径!

步骤 1 - 服务器导航和数据获取

我们首先将更新 Remix 的服务器运行时以使用 React Router 的新 unstable_createStaticHandler 来执行服务器端数据获取,一旦我们感到满意,就可以发布它,而无需触及步骤 2 到 4。更棒的是 - 我们可以将其分解为资源路由请求、客户端导航数据获取请求和文档请求的单独工作。

注意:createStaticHandler6.4.0 中被发布为不稳定的,以防我们在此过程中遇到所需的更改。一旦我们完成了 Remix 集成,我们将使其稳定。

我们计划使用 Martin Fowler 的 绞杀者无花果 模式逐个进行,这样我们就可以获得最大的信心,确保我们没有引入任何回归(向 @DavidKPiano 致敬,因为他在几个月前 重新将此模式呈现在我的脑海中!)。如果您不熟悉此模式,它的基本原理是,您在旧代码旁边编写新代码,然后慢慢地将部分代码切换过来。我们可以通过使用功能标志方法进一步推进此方法,该方法使两条路径都保持活动状态,并允许在测试和运行时进行验证。

以下是在 Remix 中,资源路由请求可能看起来像的简化示例

function handleResourceRouteRequest({ request }) {
  // If the flag is enabled, clone the request so we can use it twice
  let response = processResourceRouteRequest(
    ENABLE_NEW_STUFF ? request.clone() : request,
  );

  // When our flag is enabled, send this request through the new
  // code path, while also asserting that we get back an identical
  // response
  if (ENABLE_NEW_STUFF) {
    let newResponse = processResourceRouteRequestNew(request);
    assertResponses(response, newResponse);
    return newResponse;
  }

  return response;
}

这种方法为我们提供了许多好处

  • 功能标志允许我们在构建时剥离新的代码路径,因此我们不会在已发布的 Remix 版本中包含任何这些新代码(直到我们准备好)。
  • 因此,我们可以将此代码直接合并到 dev 分支中,而不是维护一个长期存在的特性分支。
  • 断言可用于我们所有的单元和集成测试,以确保没有任何问题,并且还可以在运行时启用以进行本地应用程序开发,这使我们能够更好地在真实的 Remix 应用程序上对其进行测试。
  • 一旦我们确信新的代码路径已准备好,我们可以将功能标志从“同时运行”方法更改为“运行一个或另一个”方法,如果我们在发布新的代码路径时出现任何问题,这将给我们留下一个非常快速的回滚策略。
  • 最后,功能标志为我们提供了清晰易懂的参考,以便在一切完成后删除 🪓。

步骤 2 - 服务器 HTML 渲染

一旦我们完成了服务器端数据获取,就可以继续进行服务器端 HTML 渲染(使用 React Router 的 unstable_StaticRouterProvider)。这也是我们可以(某种程度上)独立进行的另一个方面。我们可以使用新的 API 在服务器上渲染 HTML,但如果我们没有一些糟糕的代码分支来确定是从旧上下文还是新上下文读取,我们将无法在客户端(使用旧 API)对其进行水合。值得庆幸的是,惯用的 Remix 应用程序无需 JavaScript 即可工作,因此我们可以在此步骤中无需 JS 即可验证我们的测试和应用程序。显然,我们不会在步骤 2 之后发布,但我们能够在我们继续进行步骤 3 之前,对我们的 SSR 获得相当高的信心。同样,我们将使用一个标志来启用两条路径,并在旧路径和新路径之间执行一定程度的 HTML 断言。

这里有趣的部分是,此步骤是我们开始完全实现 Michael 的愿景的地方,即 Remix 是一个 "React 路由的编译器"。现在 react-router 知道如何执行所有酷炫的数据获取操作,Remix 只需将一组传统的磁盘上的路由文件编译成 React Router 所期望的适当路由。一旦创建了这些路由,它就会将其交给 React Router 进行繁重的工作 💪!

步骤 3 - 客户端水合

继续进行客户端水合!在这里,我们将删除绝大多数 Remix 代码(再见,过渡管理器 - 我们将永远爱你 🙃)。与上面相同,Remix 只需利用服务器提供的路由清单来生成路由树,然后将其交给 createBrowserRouter,然后 RouterProvider 完成其余的工作。超级酷的部分是 Remix 能够使用完全相同的加载器和操作来执行其所有路由,因为它们只做一件事,即使用 _data 参数对 Remix 服务器进行 fetch!这可能不会经过功能标志,因为我们不能完全对文档进行两次水合 🤷‍♂️。

步骤 4 - 客户端导航和数据获取

这可能是软件开发中最不准确的断言短语,但我们将在这里固执己见,再次使用它 - 一旦我们完成步骤 3,客户端路由和数据获取应该 正常工作™️,因为现在完全由 React Router 6.4 处理!

向后兼容性

我们再次指出,我们计划将所有这些都作为 Remix 1.x 的次要版本发布(步骤 1 可能发布一个版本,步骤 2-4 发布另一个版本)。为了保持向后兼容性,在步骤 2 和 3 中需要做一些工作。以下是一些示例

  • useTransition 在 React Router 6.4 中已重命名为 useNavigation,因此我们将标记 useTransition 为已弃用,但仍保留它。
  • React Router 扁平化了 submission 字段并从导航(以前称为过渡)和获取器中删除了 type 字段,因此我们将使用包装器钩子将它们添加回 useTransitionuseFetcher
  • React Router 没有单独的错误和捕获边界概念,因此我们将确保它们保持功能性。

在可能的情况下(我们希望这涵盖所有情况),我们将在 remix.config.js 中添加功能标志,允许您根据自己的方便选择加入新的 Remix v2 行为,同时在您不选择加入的情况下提供向后兼容的行为。

结语

我们对下一步感到非常兴奋,对它为 React Router 和 Remix 的未来带来的可能性(有人说 Preact 吗?)感到更加兴奋。请留意存储库以获取更新,一如既往,如果您有任何疑问或对所有这一切有任何兴奋之处,请随时在 DiscordTwitter 上联系我们 :)


获取有关最新 Remix 新闻的更新

成为第一个了解 Remix 新功能、社区活动和教程的人。