大家好,这里是大家的林语冰。持续关注,坚持阅读,每天一次,进步一点。
免责声明
本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考。英文原味版请传送 React Labs: What We've Been Working On。
本期共享的是 ------ React 团队的官方博客去年最新工作进展的工作报告。
RSC(React 服务器组件)
RSC(React Server Components)是 React 团队设计的一种新型 App 架构。
我们首先在官宣演讲和 RFC(征求意见稿)中共享了 RSC 的研究。温故知新,我们引入了一种新型组件 ------ RSC,它会提前运行,并被排除在 JS 打包外。RSC 可在构建时运行,让我们从文件系统读取或请求静态内容。RSC 还可以在服务器运行,让我们无需构建 API 即可读写数据层。我们可以通过 props
将数据从 RSC 传递到浏览器的交互式客户端组件。
RSC 把以服务器为中心的 MPA(多页面应用程序)的简单"请求/响应"思维模型、与以客户端为中心的 SPA(单页应用程序)的无缝交互性完美结合,为我们提供两全其美的体验。
我们合并了 RSC RFC 来批准该提案。我们解决了 React 服务器模块约定提案中的典型问题,并与合伙人达成共识,遵循 "use client"
约定。这些文档还充当 RSC 兼容实现应该支持的规范。
重大变化在于,我们引入了 async/await
作为从 RSC 请求数据的主要方案。我们还计划通过引入一个新型 Hook 来解包 Promises
,支持从客户端加载数据。尽管我们无法在纯客户端 App 的任意组件中支持 async/await
,但我们计划在大家构建类似于 RSC App 结构的纯客户端 App 时,添加对它的支持。
现在我们已经对数据请求妥当排序,我们正在另辟蹊径:将数据从客户端发送到服务器,这样我们可以执行数据库变更并实现表单。我们通过允许大家跨服务器/客户端边界传递 Server Actions(服务器操作)函数来实现,客户端可以调用该函数,提供无缝 RPC。Server Actions 还会在 JS 加载前,为我们提供渐进增强的表单。
RSC 已在 Next App 路由中提供。这展示了路由的深度集成,真正将 RSC 作为原语,但这并不是构建 RSC 兼容路由和框架的不二法门。RSC 规范和实现提供的功能泾渭分明。RSC 旨在作为跨兼容 React 框架工作的组件规范。
我们通常建议使用现有框架,但如果大家需要构建自己的自定义框架,那也问题不大。构建自己的 RSC 兼容框架并非信手拈来,主要是因为需要深度打包器集成。现代的打包器非常适合在客户端使用,但它们的设计并没有为在服务器和客户端之间拆分单个模块图提供一流支持。这就是我们现在直接与打包器开发者梦幻联动,从而获得内置 RSC 的原语的原因。
资源加载
Suspense
允许我们在组件的数据或代码仍在加载时,指定在屏幕上显示的内容。这可以在页面加载时、以及加载更多数据和代码的路由器导航期间,让我们的用户逐渐看到更多内容。虽然但是,从用户视角来看,在考虑新内容是否准备就绪时,数据加载和渲染并不能说明全部情况。默认情况下,浏览器独立加载样式表、字体和图像,这可能会导致 UI 跳转和连续布局变化。
我们正在努力将 Suspense
与样式表、字体和图像的加载生命周期完全集成,这样 React 会考虑它们,从而确定内容是否已经可以显示。在不改变大家编写 React 组件方式的情况下,更新会以更加连贯和令人愉悦的方式进行。作为优化之一,我们还会提供一种手动方式,直接从组件预加载字体等资源。
文档元数据
App 中的不同页面和屏幕可能具有不同的元数据(metadata),比如 <title>
标签、描述、以及此屏幕专属的其他 <meta>
标签。从维护的角度来看,将这些信息保留在该页面或屏幕的 React 组件附近更具可扩展性。虽然但是,此元数据的 HTML 标签需要位于该文档的 <head>
标签中,该文档通常在 App 根目录的组件中渲染。
今天,大家用两大技术中之一来解决此问题。
一种技术是渲染一个特殊的第三方组件,将其内的 <title>
、<meta>
和其他标签移动到文档的 <head>
标签中。这适用于主流浏览器,但有一大坨客户端不运行客户端 JS,比如 Open Graph 解析器,因此该技术并不普适。
另一种技术是将页面分为两部分进行 SSR(服务端渲染)。首先,渲染主要内容,并收集所有此类标签。然后,使用这些标签渲染 <head>
标签。最后,<head>
标签和主要内容被发送到浏览器。这种方法可行,但它会阻止我们利用 React 18 的流服务器渲染器,因为我们必须等待所有内容渲染后,才能发送 <head>
标签。
这就是我们在组件树中的任意位置添加对渲染 <title>
、<meta>
和元数据 <link>
标签的内置支持的原因。这在所有环境中都殊途同归,包括完全客户端代码、SSR 以及未来的 RSC。
React 优化编译器
我们一直在积极迭代 React Forget(React 的优化编译器)的设计。我们之前曾称之为"自动记忆化编译器",某种意义上而言,这名副其实。但构建编译器辅助我们更深入地理解 React 的编程模型。理解 React Forget 的最佳方案是,将其视为自动响应性编译器。
React 的核心思想是,开发者将其 UI 定义为当前状态的函数。我们可以使用纯 JS 值(数字、对象)、并使用标准 JS 习惯用法(if/for
等),来描述组件逻辑。其心智模型是,只要 App 状态变化,React 就会重新渲染。我们坚信这种简单的心智模型和保持接近 JS 语义,是 React 编程模型的重要原则。
问题在于,React 有时可能过于被动:它可能会过多重新渲染。举个栗子,在 JS 中,我们没有廉价方案来比较两个对象是否等价,比如具有相同的键值,因此为每次渲染创建一个新的对象,严格而言可能会导致 React 做多余的无用功。这意味着,开发者必须显式记忆化组件,以免对变更过度反应。
我们使用 React Forget 的目标在于,确保 React App 默认具有适量的响应性:当且仅当状态值发生有意义的变化时,App 才会重新渲染。从实现的角度来看,这意味着自动记忆化,但我们相信响应性框架是理解 React 和 Forget 的更好方案。思考此问题的方法之一是,React 现在会在对象标识更改时重新渲染。借助 Forget,React 会在语义值变化时重新渲染,但不会产生深度比较的运行时成本。
就具体进展而言,我们对编译器的设计大幅迭代,从而与这种自动响应性方案对齐,并纳入内部使用编译器的反馈。从去年年底开始,对编译器重量级重构之后,我们现在开始在 Meta(前脸书)有限区域的生产中使用此编译器。一旦我们在生产中公证有效,我们计划将其开源。
最后,一大坨道友对编译器的工作原理感兴趣。当我们验证编译器并将其开源时,我们期待共享更多细节。但我们现在可以爆料某些内容:
编译器核心几乎与 Babel 完全解耦,核心编译器 API(大致)是旧 AST 输入,新 AST 输出(同时保留源码位置数据)。在底层,我们使用自定义代码表示和转换管道来分析低级语义。虽然但是,编译器的主要公共接口会通过 Babel 和其他构建系统插件实现。为了便于测试,我们目前有一个 Babel 插件,它是一个非常小型的包装器,它调用编译器来生成每个函数的新版本,并将其交换。
在过去几个月重构编译器时,我们期望聚焦于完善核心编译模型,确保我们能够处理条件、循环、重新赋值和变更等复杂性。虽然但是,JS 存在一大坨方案来表达这些功能:if/else
、三元组、for-of
等。尝试预先支持完整的语言会延迟我们可以实现的目标 ------ 验证核心模型。相反,我们从该语言的一个"麻雀虽小、五脏俱全"的子集开始:let/const
、if/else
、for
循环、对象、函数调用和若干其他功能。随着我们对核心模型的信心倍增,并完善我们的内部抽象,我们扩展了支持的语言子集。我们还明确指出尚不支持的语法、日志诊断,并跳过不支持的输入的编译。我们有实用程序可以在 Meta 的代码库上尝试编译器,看看哪些不支持的功能最常见,这样我们就可以优先考虑后续功能。我们会循序渐进地扩展,直到支持整个语言。
在 React 组件中制作简单的 JS 响应式需要编译器对语义有深度理解,这样它才能精准理解代码的行为。通过采用这种方案,我们正在创建一个 JS 响应性系统,让大家可以使用语言的完整表达能力编写任何复杂性的产品代码,而不受限于领域特定语言。
离屏渲染
离屏渲染是 React 中即将推出的一项功能,用于在后台渲染屏幕,而无需额外的性能开销。大家可以将其视为 content-visibility
CSS 属性的一个版本,它不仅适用于 DOM 元素,也适用于 React 组件。在我们的研究过程中,我们发现了各种用例:
- 路由可以在后台预渲染屏幕,这样当用户导航到它们时,它们立即可用。
- 选项卡切换组件可以保留隐藏选项卡的状态,这样用户可以在它们之间切换不会丢失进度。
- 虚拟列表组件可以在可见窗口上下方预渲染附加行。
- 打开模式或弹出窗口时,App 的其余部分可以置于"后台"模式,这样对除模式之外的所有内容禁用事件和更新。
大多数 React 开发者不会直接与 React 屏幕外 API 的交互。相反,离屏渲染将被集成到路由和 UI 库之类的东东中,然后使用这些库的开发者会自动受益,而无需额外的工作。
此想法指的是,我们应该能够在屏幕外渲染任何 React 树,而无需更改编写组件的方式。当组件在屏幕外渲染时,它实际上不会安装,直到该组件变得可见 ------ 它的 effect(作用)不会被触发。举个栗子,如果某个组件在首次出现时使用 useEffect
来打印分析,那么预渲染不会影响这些分析的准确性。同样,当组件离开屏幕时,其 effect 也会被卸载。离屏渲染的一个关键功能在于,我们可以切换组件的可见性,而不会丢失其状态。
我们在 Android 和 iOS 的 React Native App 中,于 Meta 内部测试了预渲染的实验版本,并取得了积极的性能成果。我们还改进了离屏渲染与 Suspense
的配合方式 ------ 在离屏树内暂停,不会触发 Suspense
回退。我们余下的工作包括最终确定向库开发者公开的原语。我们预计在今年晚些时候发布 RFC,以及用于测试和反馈的实验性 API。
转换追踪
Transition Tracing API 可让我们检测 React Transitions 何时变慢,并调查它们变慢的原因。我们完成了此 API 的初步设计,并发布了 RFC。基本功能也已经实现。该项目目前处于搁置状态。我们欢迎对 RFC 的反馈,并期待恢复其开发,为 React 提供更好的性能测量工具。这对于构建在 React Transitions 之上的路由器(比如 Next App 路由)特别有用。
本期话题是 ------ React 18 用的最爽、最期待的新功能是哪一个?
欢迎在本文下方群聊自由言论,文明共享。谢谢大家的点赞,掰掰~
《前端 9 点半》每日更新,持续关注,坚持阅读,每天一次,进步一点。