跳到主要内容

React19

2025年01月04日
柏拉文
越努力,越幸运

前言


React 19 更新文档

一、ref 更新


1.1 ref Prop

React 19 开始,你现在可以在函数组件中将 ref 作为 prop 进行访问:

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

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

新的函数组件将不再需要 forwardRef,我们将发布一个 codemod 来自动更新你的组件以使用新的 ref prop。在未来的版本中,我们将弃用并移除 forwardRef

1.2 ref 清理函数

refs 支持清理函数, 这将使得在 ref 改变时执行清理操作变得更加容易。例如,你可以在 ref 改变时取消订阅事件:

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

// 新特性: 当元素从 DOM 中被移除时
// 返回一个清理函数来重置 ref
return () => {
// ref cleanup
};
}}
/>

当组件卸载时,React 将调用从 ref 回调返回的清理函数。这适用于 DOM refs,类组件的 refs,以及 useImperativeHandle

二、Hooks 更新


2.1 useDeferredValue 初始化 value

我们为 useDeferredValue 添加了一个 initialValue 选项

function Search({deferredValue}) {
// On initial render the value is ''.
// Then a re-render is scheduled with the deferredValue.
const value = useDeferredValue(deferredValue, '');

return (
<Results query={value} />
);
}

当提供了 initialValue, useDeferredValue 将在组件的初始渲染中返回它作为 value , 并在后台安排一个使用返回的 deferredValue 重新渲染。

三、支持样式表


样式表,无论是外部链接的 (<link rel="stylesheet" href="...">) 还是内联的 (<style>...</style>),都需要在 DO`M 中进行精确的定位,因为样式优先级规则。构建一个允许在组件内部进行组合的样式表功能是困难的,所以用户通常要么将所有的样式远离可能依赖它们的组件加载,要么使用一个封装了这种复杂性的样式库。

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>
)
}

其实就是设置 as 属性和 rel="preload" 属性

四、支持异步脚本


React 19 中,我们通过允许你在组件树的任何位置,即实际依赖脚本的组件内部,渲染它们,从而为异步脚本提供了更好的支持,无需管理脚本实例的重新定位和去重。

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

一般情况下,当执行到 script 标签时会进行 下载 + 执行 两步操作,这两步会阻塞 HTML 的解析, asyncdefer 能将 script 的下载阶段变成异步执行(和 html 解析同步进行)。async 异步执行脚本 脚本并行下载,一旦下载完毕立即执行,执行顺序不可预测。适合那些无需等待 DOM 完全加载、不依赖执行顺序的脚本。defer 推迟执行脚本 脚本并行下载、顺序执行,并在页面解析完毕后执行。适合那些依赖于完整 DOM 的脚本。

五、支持文档元数据


HTML 中,像 <title><link><meta> 这样的文档元数据标签被保留在文档的 <head> 部分。在 React 中,决定应用程序适合的元数据的组件可能与你渲染 <head> 的地方相距甚远,或者 React 根本不渲染 <head>。在过去,这些元素需要在效果中手动插入,或者通过像 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 和服务器组件一起工作。

六、支持预加载资源


在初始文档加载和客户端更新时,尽早告诉浏览器它可能需要加载的资源,可以显著提高页面性能。React 19 包含了一些新的 API,用于加载和预加载浏览器资源,使得构建不受资源加载效率影响的优秀体验变得尽可能容易。

import {
preinit,
preload,
preconnect,
prefetchDNS,
preinitModule,
preloadModule,
} from "react-dom";

function App() {
preconnect("https://...");
prefetchDNS("https://...");
preload("https://.../path/to/font.woff", { as: "font" });
preload("https://.../path/to/stylesheet.css", { as: "style" });
preinit("https://.../path/to/some/script.js", { as: "script" });
preinitModule("https://example.com/module-preinit.js", { as: "script" });
preloadModule("https://example.com/module-preload.js", { as: "script" });

return (
<div className="app">
<h1>React 19 新特性</h1>
</div>
);
}

export default App;

preinit: 设置 async 属性, 与 HTML 解析同时进行, 并行下载, 一旦下载完毕立即异步执行。一般情况下,当执行到 script 标签时会进行 下载 + 执行 两步操作,这两步会阻塞 HTML 的解析, asyncdefer 能将 script 的下载阶段变成异步执行(和 html 解析同步进行)。async 异步执行脚本 脚本并行下载,一旦下载完毕立即执行,执行顺序不可预测。适合那些无需等待 DOM 完全加载、不依赖执行顺序的脚本。defer 推迟执行脚本 脚本并行下载、顺序执行,并在页面解析完毕后执行。适合那些依赖于完整 DOM 的脚本。

preload: 为 link 标签设置 rel="preload" 属性进行相应优先级的预加载。例如 preload as =“style” 将获得最高优先级。

preconnect: 设置 link 标签的 rel="preconnect" 属性。通过抢先执行部分或全部握手(HTTPDNS+TCPHTTPSDNS+TCP+TLS),预连接可加快未来从给定源加载的速度。

prefetchDNS: 设置 link 标签的 rel="dns-prefetch" 属性。dns-prefetch 尝试在请求资源之前解析域名。这可能是后面要加载的文件,也可能是用户尝试打开的链接目标。当浏览器从(第三方)服务器请求资源时,必须先将该跨源域名解析为 IP 地址,然后浏览器才能发出请求。此过程称为 DNS 解析。虽然 DNS 缓存可以帮助减少此延迟,但 DNS 解析可能会给请求增加明显的延迟。对于打开了与许多第三方的连接的网站,此延迟可能会大大降低加载性能。dns-prefetch 可帮助开发人员掩盖 DNS 解析延迟。

preinitModule: 设置 async 属性, 与 HTML 解析同时进行, 并行下载, 一旦下载完毕立即异步执行。并设置 type=module 支持加载 ESM 模块。

preloadModule: 设置 link 标签的 rel="modulepreload" 属性。对于原生 ESM 模块,浏览器提供了 modulepreload来进行预加载, 资源优先级为 High

6.1 扩展知识 网络加载优先级

Chrome浏览器中的5层优先级指的是网络请求的优先级,这些优先级决定了浏览器在处理网络请求时的顺序和优先级。以下是Chrome浏览器中网络请求的5个优先级:

  • 最高优先级(Highest: 该优先级通常用于处理关键资源,如页面的主要 HTMLJavaScriptCSS 文件。这些资源对于页面的加载和渲染至关重要。

  • 较高优先级(High: 该优先级通常用于处理一些重要的资源,如页面中需要立即展示的图片、字体等。

  • 默认优先级(Medium: 大多数网络请求的默认优先级,包括大多数的静态资源和异步加载的资源。

  • 较低优先级(Low: 该优先级通常用于处理一些不太重要的资源,如异步统计脚本、广告等。

  • 最低优先级(Lowest: 该优先级通常用于处理一些可延迟加载的资源,如某些后台任务、预取资源等。

资源预加载(preload 是一种浏览器优化技术,用于在页面加载过程中提前加载 当前页面 所需的关键资源,以加速页面渲染和提高用户体验。通过使用 preload,开发者可以指定一些重要资源在页面加载时优先获取,以便在需要时能够立即使用。指定用户代理必须根据 as 属性给出的潜在目的地(以及与相应目的地相关的优先级),为当前导航预先获取和缓存目标资源。资源预加载 强调的是对当前页面关键资源的提前加载。preload 加载的是本页即将要使用的资源, 因此优先级要比 prefetch 高。但具体是多少,要看加载的资源的类型:

  • 其中,JSCSS和字体文件 preload 的优先级是High, 而image类型的资源的优先级是Low,这个和页面中JS资源的加载不同,对于页面内直连的JS文件,其加载优先级是Medium,而IMG图片则是HighCSSHighest

  • 使用as属性预加载的资源将具有与它们请求的资源类型相同的资源优先级。 例如 preload as =“style” 将获得最高优先级,而 as =“script” 将获得低优先级或中优先级。 这些资源也遵循相同的CSP策略(例如脚本受 script-src 约束)。不带 as 属性的 preload 的优先级将会等同于异步请求。

preload("https://.../path/to/stylesheet.css", { as: "style" });

<link rel="preload" href="https://.../path/to/stylesheet.css" as="style" />

七、更好的错误报告


React 19 中,我们改进了错误处理,以消除重复并提供处理捕获和未捕获错误的选项。例如,当在由错误边界捕获的渲染中出现错误时,以前 React 会抛出两次错误(一次是原始错误,然后在自动恢复失败后再次抛出),然后调用 console.error 提供错误发生的信息。这导致每个捕获的错误都有三个错误。在 React 19 中,我们记录一个包含所有错误信息的单一错误。

此段代码介绍了 React 19 中添加的两个新的根选项,用于补充 onRecoverableError:

  • onCaughtError:当 React 在错误边界中捕获错误时调用。

  • onUncaughtError:当抛出错误并且未被错误边界捕获时调用。

  • onRecoverableError:当抛出错误并自动恢复时调用。

八、支持自定义元素


React 19 添加了对自定义元素的全面支持,并通过了 Custom Elements Everywhere 上的所有测试。

在过去的版本中,使用 React 中的自定义元素很困难,因为 React 将无法识别的 props 视为 HTML attribute 而不是 DOM property。在 React 19 中,我们添加了对 DOM property 的支持,该支持在客户端和 SSR 期间都有效。

Web Components 标准非常重要的一个特性是,它使开发者能够将 HTML 页面的功能封装为 custom elements(自定义标签),而往常,开发者不得不写一大堆冗长、深层嵌套的标签来实现同样的页面功能。具体工作流如下:

一、创建自定义元素类: 继承原始类 HTMLElement 或者已有内置元素类 HTMLUListElement

二、创建 Shadow Root 影子 Root: 通过调用元素的 attachShadow 方法,可以为该元素创建一个 Shadow Root。这相当于在元素内部开辟一块独立的 DOM 区域。Shadow DOM(影子 DOMWeb Components 技术中的核心概念之一, 它允许你将一个 DOM 子树与主文档 DOM 隔离开来,从而实现组件级别的封装。Shadow DOM 可以将一个元素的内部结构、样式和行为与外部文档分离开。这样一来,组件内部的样式不会受到全局样式的干扰,也不会影响到外部的样式。通过 Shadow DOM, 你可以创建自包含的组件。组件内部的实现细节对外部是不可见的,仅暴露必要的接口或插槽(slot)给外部使用。其中, open 模式, 可以通过 element.shadowRoot 访问 Shadow DOM; closed 模式, 外部无法访问 Shadow DOM, 只能在内部使用。

三、封装与插槽: 在 Shadow DOM 内部,可以定义组件的结构和样式,同时可以利用 <slot> 标签来指定组件的插槽区域,将外部内容插入到 Shadow DOM 内部,实现灵活的内容分发和组件组合。Shadow DOM 内部的 CSS 仅对其内部元素生效,避免了全局样式的污染。同时,外部样式默认也不会影响 Shadow DOM 内部的元素。

四、注册自定义元素: customElementsWindow 对象上的一个只读属性,接口返回一个 CustomElementRegistry 对象的引用,可用于注册新的 custom element, 或者获取之前定义过的自定义元素的信息。customElements.define() 方法用于定义一个自定义元素。