上一次升级还是在上一次,React 19 技术改良的细节

发布于:2024-04-28 ⋅ 阅读:(21) ⋅ 点赞:(0)

给前端以福利,给编程以复利。大家好,我是大家的林语冰。

随着 React 19 Beta(公测版)正式上线,React API 推陈出新,一大波技术改良正在接近。

在这篇 React 官方博客中,我们会科普 React 团队在 React 19 中技术改良的细节。

react-logo.png

免责声明

本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考。英文原味版请传送 。

ref 作为 prop

从 React 19 开始,我们现在可以访问 ref 作为函数组件的 prop:

function MyInput({ placeholder, ref }) {
  return <input placeholder={placeholder} ref={ref} />
}

//...
;<MyInput ref={ref} />

新型的函数式组件将不再需要 forwardRef,我们将发布一个 codemod(代码修改)来自动更新组件,以使用全新的 ref 属性。在未来的版本中,我们将弃用并删除 forwardRef

粉丝请注意,传递给类的 refs 不会作为 props 传递,因为它们会引用组件实例

水合错误的差异

我们还改进了 react-dom 中水合错误的错误报告。

举个栗子,以前会在开发中打印多个错误,而没有任何相关不匹配的信息:

01-dev.png

现在我们只打印带有不匹配差异的单一消息:

02-diff.png

<Context> 作为 provider

在 React 19 中,我们可以将 <Context> 渲染为 provider,而不是 <Context.Provider>

const ThemeContext = createContext('')

function App({ children }) {
  return <ThemeContext value="dark">{children}</ThemeContext>
}

新型的 Context provider 可以使用 <Context>,我们将发布一个 codemod 来转换现有的提供程序。在未来的版本中,我们将弃用 <Context.Provider>

refs 的清理函数

我们现在支持从 ref 回调函数中返回 cleanup 清理函数:

<input
  ref={ref => {
    // 创建 ref

    // 新功能:返回清理函数
    // 当元素从 DOM 中删除时,ref 会重置
    return () => {
      // 清理 ref
    }
  }}
/>

当组件卸载时,React 将调用从 ref 回调函数中返回的清理函数。这适用于 DOM ref、类组件的 refuseImperativeHandle

粉丝请注意,以前 React 在卸载组件时会使用 null 调用 ref 函数。如果您的 ref 返回了清理函数,React 现在将跳过此步骤

在未来的版本中,当卸载组件时,我们将废用 null 调用 refs

由于引入了 ref 清理函数,从 ref 回调函数中返回任何其他东东现在都将被 TS 拒绝。解决方法通常是停止使用隐式返回。

举个栗子:

- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />

原始代码返回了 HTMLDivElement 的实例,TS 不知道这是否应该作为清理函数,或者您是否不想返回清理函数。

您可以使用 no-implicit-ref-callback-return 重构此模式。

useDeferredValue 的初始值

我们在 useDeferredValue 中添加了 initialValue 选项:

function Search({ deferredValue }) {
  // 初始渲染时,value 是 ''.
  // 然后使用 deferredValue 调度重新渲染。
  const value = useDeferredValue(deferredValue, '')

  return <Results query={value} />
}

当提供了 initialValue 时,useDeferredValue 会将其作为 value 返回,用于组件的初始渲染,并在后台使用返回的deferredValue 调度重新渲染。

支持文档元数据

在 HTML 中,<title><link><meta> 等文档元数据标记被保留放置在文档的 <head> 部分中。

在 React 中,决定哪些元数据适合应用程序的组件可能距离渲染 <head> 的位置很远,或者 React 根本不渲染 <head>

在过去,这些元素需要手动插入到 effect 中,或者通过 react-helmet 之类的库插入,且在服务器渲染 React 应用时需要仔细处理。

在 React 19 中,我们添加了对在组件中原生渲染文档元数据标签的支持:

function BlogPost({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <title>{post.title}</title>
      <meta name="author" content="Josh" />
      <link rel="author" href="https://twitter.com/joshcstory/" />
      <meta name="keywords" content={post.keywords} />
      <p>Eee equals em-see-squared...</p>
    </article>
  )
}

当 React 渲染这个组件时,它会看到 <title><link><meta> 标签,并自动将它们提升到文档的 <head> 部分。

通过原生支持这些元数据标签,我们能够确保它们与客户端专属的应用程序、流式 SSR 和服务器组件强强联手。

粉丝请注意,您可能仍然需要 Metadata(元数据)库

对于简单用例,将文档元数据渲染为标签可能恰如其分,但是库可以提供更强大的功能,比如使用基于当前路由的专属元数据覆盖通用元数据。这些功能使 react-helmet 等框架和库更容易支持元数据标签,而不是替换它们。

支持样式表

由于样式优先级规则,外部链接 ( <link rel="stylesheet" href="..."> ) 和内联 ( <style>...</style> ) 样式表都需要在 DOM 中仔细定位。

构建允许组件内可组合性的样式表功能难如脱单,因此用户通常最终要么加载远离可能依赖于它们的组件的所有样式,要么使用封装这种复杂性的样式库。

在 React 19 中,我们正在解决这种复杂性,并提供对客户端并发渲染和服务器上流式渲染的更深入集成,并内置对样式表的支持。

如果告诉 React 你的样式表的 precedence,它将管理 DOM 中样式表的插入顺序,并确保在显示依赖于这些样式规则的内容之前加载外部样式表。

function ComponentOne() {
  return (
    <Suspense fallback="loading...">
      <link rel="stylesheet" href="foo" precedence="default" />
      <link rel="stylesheet" href="bar" precedence="high" />
      <article class="foo-class bar-class">
        {...}
      </article>
    </Suspense>
  )
}

function ComponentTwo() {
  return (
    <div>
      <p>{...}</p>
      <link rel="stylesheet" href="baz" precedence="default" />  <-- will be inserted between foo & bar
    </div>
  )
}

在服务器端渲染期间,React 将在 <head> 中包含样式表,这确保浏览器在加载之前不会绘制。

如果样式表是在我们开始流式传输之后才发现的,React 将确保在显示依赖于该样式表的 Suspense 边界的内容之前,将样式表插入到客户端上的 <head> 中。

在客户端渲染期间,React 将等待新渲染的样式表加载,然后再提交渲染。如果您从应用程序中的多个位置渲染此组件,React 将只在文档中包含一次样式表:

function App() {
  return (
    <>
      <ComponentOne />
      ...
      <ComponentOne />
      // 不会导致 DOM 中出现重复的样式表链接
    </>
  )
}

对于习惯于手动加载样式表的用户而言,这是一个将这些样式表与依赖于它们的组件一起定位的机会,从而可以更好地进行本地推理,并更轻松地确保您只加载实际依赖的样式表。

样式库和与打包器的样式集成也可以采用这个新功能,因此即使您不直接渲染自己的样式表,您仍然可以受益,因为您的工具升级来使用此功能。

支持异步脚本

在 HTML 中,普通脚本 ( <script src="..."> ) 和延迟脚本 ( <script defer="" src="..."> ) 按文档顺序加载,这使得在组件树深处渲染这些类型的脚本具有挑战性。

然而,异步脚本 ( <script async="" src="..."> ) 将以任意顺序加载。

在 React 19 中,我们对异步脚本提供了更好的支持,允许您在组件树中的任意位置、实际依赖于脚本的组件内渲染它们,而无需管理重新定位和数据去重的脚本实例。

function MyComponent() {
  return (
    <div>
      <script async={true} src="..." />
      Hello World
    </div>
  )
}

function App() {
  <html>
    <body>
      <MyComponent>
      ...
      <MyComponent>
        // 不会导致 DOM 中出现重复的 script
    </body>
  </html>
}

在所有渲染环境中,异步脚本都会被去重,这样即使脚本由多个不同的组件渲染,React 也只会加载并执行一次脚本。

在服务器端渲染中,异步脚本将包含在 <head> 中,并优先位于阻塞绘制的更关键资源之后,例如样式表、字体和图像预加载等资源。

支持预加载资源

在初始文档加载和客户端更新期间,告诉浏览器可能需要尽早加载的资源,可能会对页面性能产生巨大影响。

React 19 包含许多用于加载和预加载浏览器资源的全新 API,以便尽可能轻松地构建不受低效资源加载阻碍的出色体验。

import { prefetchDNS, preconnect, preload, preinit } from 'react-dom'
function MyComponent() {
  preinit('https://.../path/to/some/script.js', { as: 'script' }) // 迫切加载并执行此脚本
  preload('https://.../path/to/font.woff', { as: 'font' }) // 预加载此字体
  preload('https://.../path/to/stylesheet.css', { as: 'style' }) // 预加载此样式表
  prefetchDNS('https://...') // 当您实际上可能没有向该主机请求内容时
  preconnect('https://...') // 当您将请求某些东东,尚未确定请求何物时
}
<!-- 上述操作将产生以下的 DOM/HTML -->
<html>
  <head>
    <!-- link 或 script 按其实用优先级及早加载,
    而不是调用顺序 -->
    <link rel="prefetch-dns" href="https://..." />
    <link rel="preconnect" href="https://..." />
    <link rel="preload" as="font" href="https://.../path/to/font.woff" />
    <link rel="preload" as="style" href="https://.../path/to/stylesheet.css" />
    <script async="" src="https://.../path/to/some/script.js"></script>
  </head>
  <body>
    ...
  </body>
</html>

这些 API 可用于通过将字体等其他资源的发现移出样式表加载,优化初始页面加载。它们还可以通过预请求预期导航使用的资源列表,然后在单击甚至悬停时迫切预加载这些资源,加快客户端更新速度。

与第三方脚本和扩展的兼容性

我们改良了水合作用,以考虑第三方脚本和浏览器扩展。

水合时,如果客户端渲染的元素与服务器 HTML 中找到的元素不匹配,React 将强制客户端重新渲染以修复内容。以前,如果由第三方脚本或浏览器扩展插入元素,那会触发不匹配错误和客户端渲染。

在 React 19 中,<head><body> 中的意外标签将被跳过,避免不匹配错误。如果 React 由于不相关的水合作用不匹配而需要重新渲染整个文档,它将保留由第三方脚本和浏览器扩展插入的样式表。

更好的错误报告

我们改进了 React 19 中的错误处理,消除重复并提供处理捕获和未捕获错误的选项。

举个栗子,当渲染中出现错误并被错误边界捕获时,以前的 React 会报错两次:一次是针对原始错误,然后在无法自动恢复后再次报错,然后根据错误发生的信息调用 console.error

对于每个捕获的错误,这会导致三个错误:

// three.jpg

在 React 19 中,我们只打印单一错误,其中包含所有错误信息:

// single.jpg

此外,我们添加了两个全新的根选项来补充 onRecoverableError

  • onCaughtError:当 React 在错误边界中捕获错误时调用。
  • onUncaughtError:当报错且未被错误边界捕获时调用。
  • onRecoverableError:报错时调用并自动恢复。

支持自定义元素

React 19 增加了对自定义元素的全面支持,并通过了对 Custom Elements Everywhere(到处自定义元素)的所有测试。

在过去的版本中,在 React 中使用自定义元素一直难如脱单,因为 React 将无法识别的 props 视为 attribute 而不是 property。在 React 19 中,我们添加了对在客户端和 SSR 期间使用的 property 的支持,策略如下:

  • 服务器端渲染:如果传递给自定义元素的 props 类型是 stringnumber 等原始值或者值为 true,那么它们将渲染为 property。具有 objectsymbolfunction 等非原始类型或值为 false 的 props 将被忽略。
  • 客户端渲染:与自定义元素实例上的属性匹配的 prop 将被赋值为 property,否则它们将被赋值为 attribute。

参考文献

  1. React
  2. React 19 Beta
  3. facebook/react

粉丝互动

本期话题是:如何评价 React 19 改进的技术细节?你可以在本文下方自由言论,文明科普。

欢迎持续关注“前端俱乐部”,给前端以福利,给编程以复利。

坚持阅读的小伙伴可以给自己点赞!谢谢大家的点赞,掰掰~

26-cat.gif


网站公告

今日签到

点亮在社区的每一天
去签到