React Router Logo
2022 年 11 月 2 日

React Router 集成 Remix

Matt Brophy
资深开发工程师

今年早些时候,我们开始了一项将 Remix React Router 化 的工作,目标是将所有 Remix Data API(loadersactionsfetchers 等)迁移到 React Router。随着最近发布的 React Router v6.4.0,我们很自豪地宣布我们已经完成了这项工作...而且我们认为我们做得更好了😃。我们不仅修复了一些极端情况下的错误,还稳定了一些 API 并引入了一些非常棒的新 API。这里对更改进行一个快速概述,我们建议您查看博客文章以获取更多信息。

  • 🆕 使用 useRevalidator 以编程方式重新验证
  • 🆕 使用 defer/Await 分离关键/非关键数据
  • 🆕 使用 useRouteLoaderData 获取特定路由的加载器数据
  • unstable_shouldReload 已稳定为 shouldRevalidate
  • 🔥 全新的改进的 <ScrollRestoration getKey> 方法可以更精细地控制滚动恢复
  • 🔥 Catch 和 Error 边界已合并为一个 errorElement
  • 🐞 fetcher.load 调用现在参与重新验证 - 它们本就应该一直参与!

React Router 集成 Remix

现在我们可以反过来,开始**将 React Router 集成到 Remix**,这样我们就可以将这些更改带回给 Remix 用户(并在此过程中删除*大量* Remix 代码)。 对于你们所有人来说,好消息是我们计划以迭代的方式进行,而无需进行重大发布🤯。我们认为我们正在使用的方法很酷,所以想和大家分享。

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

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

这 4 个部分也恰好很好地解耦了,因此它们为我们提供了清晰的边界,以便以迭代的方式进行处理,从而避免一次性的大规模发布。 这应该意味着 Remix 用户可以获得更顺畅的集成路径!

第一步 - 服务器导航和数据获取

我们将首先更新 Remix 的服务器运行时,以使用 React Router 新的 unstable_createStaticHandler 来执行服务器端数据获取,一旦我们感到满意,我们就可以在不触及第 2 到 4 步的情况下发布它。 更棒的是,我们可以将此分解为资源路由请求、客户端导航数据获取请求和文档请求的各个工作。

注意:createStaticHandler6.4.0 中作为不稳定版本发布,以防在此过程中遇到需要的更改。 一旦我们完成 Remix 集成,我们将稳定它。

我们计划使用 Martin Fowler 的 绞杀榕 模式逐个进行,这样我们就可以最大限度地确信我们没有引入任何回归(感谢 @DavidKPiano 在我脑海中重新浮现这种模式 几个月前!)。如果你不熟悉这种模式,它的基本要点是你将新代码与旧代码一起编写,然后慢慢切换部分代码。我们可以通过 feature-flag 方法进一步做到这一点,该方法保持两条路径都处于活动状态,并允许在测试和运行时进行验证。

这是一个简化的示例,说明这在 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;
}

这种方法为我们带来了许多好处

  • feature-flag 允许我们在构建时剥离新代码路径,因此我们不会在已发布的 remix 版本中包含任何新代码(直到我们准备好为止)
  • 因此,我们可以直接将此代码合并到 dev 分支中,而不是维护一个长期存在的功能分支
  • 断言可以用于我们所有的单元测试和集成测试,以确保没有任何东西被破坏,并且也可以在本地应用程序开发期间在运行时启用,这使我们能够更好地在实际的 Remix 应用程序上测试这一点
  • 一旦我们确信新代码路径已准备就绪,我们可以将 feature-flag 从“同时运行”方法更改为“运行其中一个”方法,如果我们在发布新代码路径时出现任何问题,这将为我们提供非常快速的回滚策略
  • 最后,feature-flag 为我们提供了清晰而简单的参考,以便在我们完成所有操作后删除它们 🪓

第二步 - 服务器 HTML 渲染

完成服务器端数据获取后,我们可以继续进行服务器端 HTML 渲染(使用 React Router 的 unstable_StaticRouterProvider)。 这是我们可以(某种程度上)独立执行的另一个方面。 我们可以使用新的 API 在服务器上渲染 HTML,但是我们将无法在客户端(使用旧的 API)上进行 hydration,而无需一些丑陋的代码分支来确定是从旧上下文还是新上下文中读取。 值得庆幸的是,惯用的 Remix 应用程序可以在没有 JavaScript 的情况下工作,因此我们可以在此步骤中验证我们的测试和应用程序,而无需 JS。 显然,我们不会在第二步之后发布,但是我们可以在进入第三步之前对 SSR 获得相当高的信心。 我们将再次使用标志来启用两条路径并在新旧之间执行某种级别的 HTML 断言。

这里有趣的部分是,这一步是我们开始充分实现 Michael 对 Remix 成为 “React Router 编译器” 愿景的地方。 现在 react-router 知道如何完成所有很酷的数据获取工作,Remix 只是将磁盘上的一组常规路由文件编译成 React Router 期望的适当路由。 创建这些路由后,它只需将它们交给 React Router 进行繁重的工作💪!

第三步 - 客户端 Hydration

继续进行客户端 hydration! 在这里,我们将删除大部分 Remix 代码(再见 Transition Manager - 我们会永远爱你 🙃)。 与上面类似,Remix 只需要利用服务器提供的路由清单来生成一个路由树,将其交给 createBrowserRouter,然后 RouterProvider 完成其余的工作。 *超级*酷的部分是,Remix 可以对其所有路由使用*完全相同的 loader 和 action*,因为它们所做的只是使用 _data 参数向 Remix 服务器进行 fetch! 这可能不会通过 feature-flag,因为我们不能完全将文档 hydration 两次 🤷‍♂️。

第四步 - 客户端导航和数据获取

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

向后兼容性

我们应该再次注意,我们计划将所有这些作为次要的 Remix 1.x 版本进行(可能一个版本用于步骤 1,另一个版本用于步骤 2-4)。 为了保持向后兼容性,在步骤 2 和 3 中需要做一些工作。这里有一些例子

  • useTransition 在 React Router 6.4 中已重命名为 useNavigation,因此我们将 useTransition 标记为已弃用,但保留它
  • React Router 将 submission 字段展平,并从导航(以前的 transitions)和 fetcher 中删除了 type 字段,因此我们将使用包装 hook 将它们添加回 useTransitionuseFetcher
  • React Router 没有单独的 Error 和 Catch 边界的概念,因此我们将确保它们保持功能

在可能的情况下(我们希望所有情况都是如此),我们将向 remix.config.js 添加 feature-flag,使你可以根据自己的方便选择加入新的 Remix v2 行为,同时在不选择加入的情况下提供向后兼容的行为。

结尾

我们对下一步感到非常兴奋,并且对它为 React Router 和 Remix 的未来带来的一些可能性(有人说 Preact 吗?)感到更加兴奋。 请密切关注仓库中的更新,并一如既往地在 DiscordTwitter 上与我们联系,如果你对所有这些有任何疑问或兴奋之处 :)


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

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