React 实验室:我们一直在做的工作 – 2024年2月

2024年2月15日,由Joseph SavonaRicky HanlonAndrew ClarkMatt CarrollDan Abramov撰写。

🌐 February 15, 2024 by Joseph Savona, Ricky Hanlon, Andrew Clark, Matt Carroll, and Dan Abramov.


在 React Labs 的文章中,我们会撰写关于正在进行研究和开发的项目。自从我们上次更新以来,我们已经取得了重大进展,我们希望分享我们的进展情况。

🌐 In React Labs posts, we write about projects in active research and development. We’ve made significant progress since our last update, and we’d like to share our progress.


React 编译器

🌐 React Compiler

React 编译器不再是一个研究项目:该编译器现在在生产环境中为 instagram.com 提供支持,我们正在努力将该编译器推广到 Meta 的更多平台,并准备第一次开源发布。

🌐 React Compiler is no longer a research project: the compiler now powers instagram.com in production, and we are working to ship the compiler across additional surfaces at Meta and to prepare the first open source release.

正如我们在上一篇文章中讨论的,当状态发生变化时,React有时可能会过度重新渲染。自React的早期阶段以来,我们对这种情况的解决方案一直是手动记忆化。在我们当前的API中,这意味着应用useMemouseCallbackmemo API来手动调整React在状态变化时的重新渲染次数。但手动记忆化是一种折中方案。它会使我们的代码变得杂乱,容易出错,并且需要额外的工作来保持更新。

🌐 As discussed in our previous post, React can sometimes re-render too much when state changes. Since the early days of React our solution for such cases has been manual memoization. In our current APIs, this means applying the useMemo, useCallback, and memo APIs to manually tune how much React re-renders on state changes. But manual memoization is a compromise. It clutters up our code, is easy to get wrong, and requires extra work to keep up to date.

手动记忆化是一种合理的折中方案,但我们并不满足。我们的愿景是让 React 在状态变化时能够自动重新渲染 UI 的恰当部分,而不影响 React 的核心思维模型。我们相信,React 的方法——将 UI 视为状态的简单函数,并使用标准的 JavaScript 值和习惯用法——是 React 能够为众多开发者所接受的关键原因。这就是为什么我们投入精力为 React 构建一个优化编译器的原因。

🌐 Manual memoization is a reasonable compromise, but we weren’t satisfied. Our vision is for React to automatically re-render just the right parts of the UI when state changes, without compromising on React’s core mental model. We believe that React’s approach — UI as a simple function of state, with standard JavaScript values and idioms — is a key part of why React has been approachable for so many developers. That’s why we’ve invested in building an optimizing compiler for React.

由于 JavaScript 的规则宽松且具有动态特性,它是一种公认的难以优化的语言。React 编译器能够通过模拟 JavaScript 的规则以及“React 的规则”来安全地编译代码。例如,React 组件必须是幂等的——在相同输入下返回相同的值——并且不能更改 props 或 state 的值。这些规则限制了开发者的操作,并帮助为编译器优化创建一个安全空间。

🌐 JavaScript is a notoriously challenging language to optimize, thanks to its loose rules and dynamic nature. React Compiler is able to compile code safely by modeling both the rules of JavaScript and the “rules of React”. For example, React components must be idempotent — returning the same value given the same inputs — and can’t mutate props or state values. These rules limit what developers can do and help to carve out a safe space for the compiler to optimize.

当然,我们理解开发者有时会稍微打破规则,我们的目标是让 React 编译器在尽可能多的代码上开箱即用。编译器会尝试检测代码何时没有严格遵循 React 的规则,并在安全的情况下编译代码,或在不安全的情况下跳过编译。我们正在对 Meta 的大型多样化代码库进行测试,以帮助验证这种方法。

🌐 Of course, we understand that developers sometimes bend the rules a bit, and our goal is to make React Compiler work out of the box on as much code as possible. The compiler attempts to detect when code doesn’t strictly follow React’s rules and will either compile the code where safe or skip compilation if it isn’t safe. We’re testing against Meta’s large and varied codebase in order to help validate this approach.

对于那些好奇如何确保自己代码遵循 React 规则的开发者,我们建议启用严格模式配置 React 的 ESLint 插件。这些工具可以帮助捕捉 React 代码中的细微错误,提高你应用的质量,并为未来功能(如 React 编译器)做好准备。我们还在致力于汇总 React 规则的文档,并更新我们的 ESLint 插件,以帮助团队理解和应用这些规则,从而创建更健壮的应用。

🌐 For developers who are curious about making sure their code follows React’s rules, we recommend enabling Strict Mode and configuring React’s ESLint plugin. These tools can help to catch subtle bugs in your React code, improving the quality of your applications today, and future-proofs your applications for upcoming features such as React Compiler. We are also working on consolidated documentation of the rules of React and updates to our ESLint plugin to help teams understand and apply these rules to create more robust apps.

要看到编译器的实际运行情况,你可以查看我们去年秋天的演讲。在演讲时,我们已经有了在 Instagram.com 的一个页面上试用 React 编译器的早期实验数据。从那时起,我们已经将编译器推向了 Instagram.com 的生产环境。我们还扩展了团队,以加快在 Meta 的其他平台以及开源的推广。我们对未来的道路感到非常兴奋,并将在接下来的几个月中分享更多内容。

🌐 To see the compiler in action, you can check out our talk from last fall. At the time of the talk, we had early experimental data from trying React Compiler on one page of instagram.com. Since then, we shipped the compiler to production across instagram.com. We’ve also expanded our team to accelerate the rollout to additional surfaces at Meta and to open source. We’re excited about the path ahead and will have more to share in the coming months.

动作

🌐 Actions

我们之前分享过 我们正在探索通过服务器操作从客户端向服务器发送数据的解决方案,以便你可以执行数据库变更并实现表单。在开发服务器操作的过程中,我们扩展了这些 API,以支持仅客户端应用中的数据处理。

🌐 We previously shared that we were exploring solutions for sending data from the client to the server with Server Actions, so that you can execute database mutations and implement forms. During development of Server Actions, we extended these APIs to support data handling in client-only applications as well.

我们将这个更广泛的功能集合简单称为“动作”。动作允许你向 DOM 元素传递一个函数,例如 <form/>:

🌐 We refer to this broader collection of features as simply “Actions”. Actions allow you to pass a function to DOM elements such as <form/>:

<form action={search}>
<input name="query" />
<button type="submit">Search</button>
</form>

action 函数可以同步或异步运行。你可以在客户端使用标准 JavaScript 定义它们,或者在服务器端使用 'use server' 指令定义它们。在使用某个操作时,React 会为你管理数据提交的生命周期,提供像 useFormStatususeActionState 这样的钩子来访问表单操作的当前状态和响应。

🌐 The action function can operate synchronously or asynchronously. You can define them on the client side using standard JavaScript or on the server with the 'use server' directive. When using an action, React will manage the life cycle of the data submission for you, providing hooks like useFormStatus, and useActionState to access the current state and response of the form action.

默认情况下,操作会在 transition 中提交,同时保持当前页面可交互,直到操作处理完成。由于操作支持异步函数,我们还增加了在过渡中使用 async/await 的能力。这允许你在像 fetch 这样的异步请求开始时,用过渡的 isPending 状态显示待处理的 UI,并在更新应用的整个过程中显示待处理的 UI。

🌐 By default, Actions are submitted within a transition, keeping the current page interactive while the action is processing. Since Actions support async functions, we’ve also added the ability to use async/await in transitions. This allows you to show pending UI with the isPending state of a transition when an async request like fetch starts, and show the pending UI all the way through the update being applied.

除了 Actions,我们还推出了一个名为 useOptimistic 的功能,用于管理乐观状态更新。通过这个 hook,你可以应用临时更新,一旦最终状态提交,这些更新会自动回滚。对于 Actions,这允许你在客户端乐观地设置数据的最终状态,假设提交成功,并在收到服务器数据时恢复到服务器的数据值。它使用常规的 async/await 工作,所以无论你是在客户端使用 fetch,还是从服务器使用 Server Action,都能以相同的方式工作。

🌐 Alongside Actions, we’re introducing a feature named useOptimistic for managing optimistic state updates. With this hook, you can apply temporary updates that are automatically reverted once the final state commits. For Actions, this allows you to optimistically set the final state of the data on the client, assuming the submission is successful, and revert to the value for data received from the server. It works using regular async/await, so it works the same whether you’re using fetch on the client, or a Server Action from the server.

库作者可以使用 useTransition 在他们自己的组件中实现自定义的 action={fn} 属性。我们的意图是让库在设计组件 API 时采用 Actions 模式,以为 React 开发者提供一致的体验。例如,如果你的库提供了一个 <Calendar onSelect={eventHandler}> 组件,也可以考虑同时提供一个 <Calendar selectAction={action}> API。

🌐 Library authors can implement custom action={fn} props in their own components with useTransition. Our intent is for libraries to adopt the Actions pattern when designing their component APIs, to provide a consistent experience for React developers. For example, if your library provides a <Calendar onSelect={eventHandler}> component, consider also exposing a <Calendar selectAction={action}> API, too.

虽然我们最初专注于用于客户端与服务器数据传输的服务器操作,但我们对 React 的理念是在所有平台和环境中提供相同的编程模型。在可能的情况下,如果我们在客户端引入某个功能,我们的目标是让它也能在服务器上工作,反之亦然。这一理念使我们能够创建一套在任何应用运行环境下都能使用的 API,从而让以后升级到不同环境变得更容易。

🌐 While we initially focused on Server Actions for client-server data transfer, our philosophy for React is to provide the same programming model across all platforms and environments. When possible, if we introduce a feature on the client, we aim to make it also work on the server, and vice versa. This philosophy allows us to create a single set of APIs that work no matter where your app runs, making it easier to upgrade to different environments later.

操作现在可以在 Canary 通道使用,并将随下一版本的 React 发布。

🌐 Actions are now available in the Canary channel and will ship in the next release of React.

React Canary 中的新功能

🌐 New Features in React Canary

我们引入了 React Canaries 作为一种选项,以便在新稳定功能的设计接近最终版本时立即采用这些功能,而无需等到它们在稳定的 semver 版本中发布。

🌐 We introduced React Canaries as an option to adopt individual new stable features as soon as their design is close to final, before they’re released in a stable semver version.

Canaries 对我们开发 React 的方式是一种改变。以前,功能会在 Meta 内部私下研究和开发,所以用户只有在发布到 Stable 时才能看到最终的成品。有了 Canaries,我们在社区的帮助下公开构建功能,以最终确定我们在 React Labs 博客系列中分享的功能。这意味着你会更早听到新功能的消息,因为是在功能完成之前即进行最终定型,而不是在功能完成之后。

🌐 Canaries are a change to the way we develop React. Previously, features would be researched and built privately inside of Meta, so users would only see the final polished product when released to Stable. With Canaries, we’re building in public with the help of the community to finalize features we share in the React Labs blog series. This means you hear about new features sooner, as they’re being finalized instead of after they’re complete.

React 服务器组件、资源加载、文档元数据和操作功能已全部在 React Canary 中推出,我们已在 react.dev 上为这些功能添加了文档:

🌐 React Server Components, Asset Loading, Document Metadata, and Actions have all landed in the React Canary, and we’ve added docs for these features on react.dev:

  • 指令"use client""use server" 是为全栈 React 框架设计的打包器功能。它们标记了两个环境之间的“拆分点”:"use client" 指示打包器生成一个 <script> 标签(类似 Astro Islands),而 "use server" 告诉打包器生成一个 POST 端点(类似 tRPC Mutations)。它们共同让你能够编写可重用组件,将客户端交互性与相关的服务器端逻辑组合在一起。
  • 文档元数据:我们为在你的组件树中的任何位置呈现 <title><meta> 和元数据 <link> 标签添加了内置支持。这些在所有环境中都以相同的方式工作,包括完全客户端代码、SSR 和 RSC。这为像 React Helmet 这样的库开创的功能提供了内置支持。
  • 资源加载:我们将 Suspense 与样式表、字体和脚本等资源的加载生命周期整合在一起,以便 React 考虑它们来确定像 <style><link><script> 这样的元素中的内容是否准备好显示。我们还添加了新的 资源加载 API,比如 preloadpreinit,以便提供更大控制,决定资源何时加载和初始化。
  • 操作:如上所述,我们已添加操作来管理从客户端向服务器发送数据。你可以将 action 添加到类似 <form/> 的元素上,通过 useFormStatus 访问状态,使用 useActionState 处理结果,并通过 useOptimistic 乐观地更新用户界面。

由于所有这些功能都是协同工作的,因此很难在稳定版本通道中单独发布它们。发布 Actions 而没有访问表单状态的补充钩子会限制 Actions 的实际可用性。在引入 React 服务器组件而不集成服务器 Actions 的情况下,会使在服务器上修改数据变得复杂。

🌐 Since all of these features work together, it’s difficult to release them in the Stable channel individually. Releasing Actions without the complementary hooks for accessing form states would limit the practical usability of Actions. Introducing React Server Components without integrating Server Actions would complicate modifying data on the server.

在我们将一组功能发布到稳定渠道之前,我们需要确保它们能够协同工作,并且开发者拥有在生产环境中使用它们所需的一切。React Canaries 允许我们单独开发这些功能,并逐步发布稳定的 API,直到整个功能集完成。

🌐 Before we can release a set of features to the Stable channel, we need to ensure they work cohesively and developers have everything they need to use them in production. React Canaries allow us to develop these features individually, and release the stable APIs incrementally until the entire feature set is complete.

React Canary 中的当前功能集已完整并准备发布。

🌐 The current set of features in React Canary are complete and ready to release.

React 的下一个主要版本

🌐 The Next Major Version of React

经过几年的迭代,react@canary 现在已准备好发货到 react@latest。上述提到的新功能与你的应用运行的任何环境兼容,提供生产使用所需的一切。由于资源加载和文档元数据可能会对某些应用造成破坏性更改,下一版本的 React 将是一个主要版本:React 19

🌐 After a couple of years of iteration, react@canary is now ready to ship to react@latest. The new features mentioned above are compatible with any environment your app runs in, providing everything needed for production use. Since Asset Loading and Document Metadata may be a breaking change for some apps, the next version of React will be a major version: React 19.

为了准备发布,还有更多工作要做。在 React 19 中,我们也在加入长期请求的改进,这些改进需要破坏性更改,如对 Web Components 的支持。我们现在的重点是落实这些更改、为发布做准备、完善新功能的文档,并发布包含内容的公告。

🌐 There’s still more to be done to prepare for release. In React 19, we’re also adding long-requested improvements which require breaking changes like support for Web Components. Our focus now is to land these changes, prepare for release, finalize docs for new features, and publish announcements for what’s included.

我们将在未来几个月分享更多关于 React 19 包含的所有内容、如何采用新的客户端功能以及如何构建对 React 服务器组件的支持的信息。

🌐 We’ll share more information about everything React 19 includes, how to adopt the new client features, and how to build support for React Server Components in the coming months.

屏幕外(已重命名为活动)。

🌐 Offscreen (renamed to Activity).

自上次更新以来,我们将正在研究的一项功能从“Offscreen”重命名为“Activity”。“Offscreen”这个名称暗示它只适用于应用中不可见的部分,但在研究该功能时,我们意识到应用的某些部分可能是可见但不活动的,例如模态窗口后的内容。新名称更准确地反映了将应用的某些部分标记为“活动”或“非活动”的行为。

🌐 Since our last update, we’ve renamed a capability we’re researching from “Offscreen” to “Activity”. The name “Offscreen” implied that it only applied to parts of the app that were not visible, but while researching the feature we realized that it’s possible for parts of the app to be visible and inactive, such as content behind a modal. The new name more closely reflects the behavior of marking certain parts of the app “active” or “inactive”.

该活动仍在研究中,我们剩下的工作是最终确定向库开发者公开的基本组件。在我们专注于交付更完整的功能时,这一字段的优先级已被降低。

🌐 Activity is still under research and our remaining work is to finalize the primitives that are exposed to library developers. We’ve deprioritized this area while we focus on shipping features that are more complete.


除了这次更新之外,我们的团队还在会议上作了报告,并出现在播客节目中,以便更多地讲述我们的工作并回答问题。

🌐 In addition to this update, our team has presented at conferences and made appearances on podcasts to speak more on our work and answer questions.

感谢 Lauren TanSophie AlpertJason BontaEli WhiteSathya Gunasekaran 审阅这篇文章。

🌐 Thanks Lauren Tan, Sophie Alpert, Jason Bonta, Eli White, and Sathya Gunasekaran for reviewing this post.

感谢阅读,在 React 大会上见

🌐 Thanks for reading, and see you at React Conf!