React 19 技术改良的细节,上一次升级还是在上一次
给前端以福利,给编程以复利。大家好,我是大家的林语冰。
随着 React 19 Beta(公测版)正式上线,React API 推陈出新,一大波技术改良正在接近。
在这篇 React 官方博客中,我们会科普 React 团队在 React 19 中技术改良的细节。
免责声明
本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考。英文原味版请传送 React 19 Beta。
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
中水合错误的错误报告。
举个栗子,以前会在开发中打印多个错误,而没有任何相关不匹配的信息:
现在我们只打印带有不匹配差异的单一消息:
<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
、类组件的 ref
和 useImperativeHandle
。
粉丝请注意,以前 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 类型是
string
和number
等原始值或者值为true
,那么它们将渲染为 property。具有object
、symbol
和function
等非原始类型或值为false
的 props 将被忽略。 - 客户端渲染:与自定义元素实例上的属性匹配的 prop 将被赋值为 property,否则它们将被赋值为 attribute。
参考文献
- React:react.dev
- React 19 Beta:react.dev/blog/2024/0…
- facebook/react:github.com/facebook/re…
粉丝互动
本期话题是:如何评价 React 19 改进的技术细节?你可以在本文下方自由言论,文明科普。
欢迎持续关注“前端俱乐部”,给前端以福利,给编程以复利。
坚持阅读的小伙伴可以给自己点赞!谢谢大家的点赞,掰掰~
转载自:https://juejin.cn/post/7362203195051982911