React 19 新增内容
Actions 异步状态处理
useTransition
优化
支持异步函数
新增 hook useActionState
该 hook 原来由 ReactDOM 包导出,由于一些原因,因此做出一些改动 useActionState 的类型如下:
typescript
type Type = function useActionState<State>(
action: (state: Awaited<State>) => State | Promise<State>,
initialState: Awaited<State>,
permalink?: string,
): [state: Awaited<State>, dispatch: () => void, boolean];
// 返回一个元组,[state, dispatch, pending]
// state: 最新的状态
// dispatch: 调用 action 的方法
// pending: action/状态更新进度 的状态
使用示例:
javascript
import { useActionState } from "react";
function Form({ formAction }) {
const [state, action, isPending] = useActionState(formAction);
return (
<form action={action}>
<input type="email" name="email" disabled={isPending} />
<button type="submit" disabled={isPending}>
Submit
</button>
{state.errorMessage && <p>{state.errorMessage}</p>}
</form>
);
}
javascript
// 为什么要更名的原因,一个解释的例子如下
// 因为原来 useFormState 不是一定要和 <form /> 一起使用,因此会有一些混乱
import { useActionState, useRef } from "react";
function Form({ someAction }) {
const ref = useRef(null);
const [state, action, isPending] = useActionState(someAction);
async function handleSubmit() {
// See caveats below
await action({ email: ref.current.value });
}
return (
<div>
<input ref={ref} type="email" name="email" disabled={isPending} />
<button onClick={handleSubmit} disabled={isPending}>
Submit
</button>
{state.errorMessage && <p>{state.errorMessage}</p>}
</div>
);
}
如果对于 CSR,其实这个 hook 并不是很有优势,这个 hook 主要还是给支持 RSC 的框架使用。 优点:
- 内部数据变化是通过 useTransition 实现的,因此在结果返回之前保持页面的响应。
- 自动跟踪包装函数的返回值和 pending 状态,无需自己去维护。
- 在 RSC 上使用的优点如下:
- 支持 replay,在水合之前如果触发了表单的 submit,该 hook 会自动重放该 submit,以保证正常的 JavaScript 的执行。
举个栗子:假设你正在填写一个网页上的表单,但你的网络连接不是很好,JavaScript 代码还在加载中。在传统的网页上,如果你在这个时刻提交表单,可能什么都不会发生,因为处理表单提交的 JavaScript 代码还没加载完成。但在使用了 React 19 的
元素和相关钩子的情况下,即使你在 JavaScript 完全加载好之前提交了表单,React 也能确保你的提交被正确处理,一旦 JavaScript 准备好了,它会自动"回放"你的提交操作。 这就像是你试图在电视信号不好的时候换台,虽然一开始画面可能会卡住,但是一旦信号稳定了,电视会自动帮你切换到你想要观看的频道。
与最新的 <form>
集成
<form />
<input />
<button />
新增 action
和 formAction
参数
新增 hook useFormStatus
对于很深层次的 form 组件,逐层传递 props 显然是不合适的,通过 context 或者轻量状态管理库是可以解决的,新增的这个 hook 相当于封装了 context。
javascript
import {useFormStatus} from 'react-dom';
function DesignButton() {
// useFormStatus 能够读取上层 form 的状态
const status = useFormStatus();
return <button type="submit" disabled={status.pending} />
}
注意点:
- 该 hook 只能获取父级的 form 的相关数据
- 返回的数据的类型:
typescript
interface Status {
pending: boolean;
data: FormData;
method: 'get' | 'post';
// 对父级form的action的引用
action: (state: Awaited<State>) => State | Promise<State>;
}
新增 hook useOptimistic
简单来说就是假设状态更新会成功,如果失败或者执行完成,然后更新 state。主要用在异步操作上,去提高用户的体验。
举个栗子:你在玩一个游戏,点击了一个按钮来升级你的装备。大多数人会期望立即看到装备升级的效果,即使服务器还需要一些时间来处理你的请求。useOptimistic 钩子就是让这种体验成为可能:你点击升级,装备立即显示为升级状态,如果一切顺利,你就保持这个状态;如果有问题,游戏会自动纠正并告诉你出了什么错。这种方式可以减少用户的等待焦虑,让他们感觉操作更加即时和流畅。
新增 API use
- 与 Promise 结合
javascript
import {use} from 'react';
function Comments({commentsPromise}) {
// use 将等待直到 Promise 由 pending 变为 fullfilled/rejected
const comments = use(commentsPromise);
return comments.map(comment => <p key={comment.id}>{comment}</p>);
}
function Page({commentsPromise}) {
// Suspense 将会触发,展示 Loading ...
return (
<Suspense fallback={<div>Loading...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
)
}
- 与 Context 结合
javascript
import {use} from 'react';
import ThemeContext from './ThemeContext'
function Heading({children}) {
if (children == null) {
return null;
}
const theme = use(ThemeContext);
return (
<h1 style={{color: theme.color}}>
{children}
</h1>
);
}
通过上面这个例子,我们可以发现 use
是可以在条件语句之后执行的,这和其他 hooks 是完全不一样的。
服务器组件 RSC
Server Actions
允许客户端组件调用服务器上执行的异步函数。通过指令来表示代码是在客户端运行 or 客户端可以调用的服务器的函数
React 19 优化内容
ref 的优化
这个爽死真的,官方写了一个 codemod,所以不需要再用 forwardRef
去获取传入的 ref。 注意:不能在类组件上使用。
jsx
function MyInput({ref}) {
return <input ref={ref} />
}
//...
<MyInput ref={ref} />
水合时出错信息的优化
将 多个错误信息但是没有用的 合并为 一条并且告知不匹配原因 的信息。
Context 的优化
也是写法的简化,就是在 create 一个 context 的时候不需要在顶层写 xxx.provider
了,直接写 xxx
即可。
jsx
const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
refs 的优化
之前的做法:
当需要设置 ref 时,React 会调用这个 ref 回调,并传入对应的 DOM 节点作为参数。当需要清除 ref 时,React 会再次调用这个 ref 回调,并传入 null 作为参数。通过使用 ref 回调,就可以在 React 组件的生命周期中执行特定的操作,比如操作 DOM、获取 DOM 属性等。
jsx
import { useRef } from 'react';
function ExampleComponent() {
const myRef = useRef(null);
function handleRef(node) {
// 在创建ref/卸载DOM时调用
if (node) {
// 执行你的操作,比如操作 DOM
console.log(node);
} else {
// 在清除 ref 时调用,此时 node 是 null
console.log('Ref is cleared');
}
}
return (
<div ref={handleRef}>
{/* ... */}
</div>
);
}
优化后:
可以像 useEffect
那样返回一个清理函数
jsx
<input
ref={(node) => {
// ref 创建的时候执行该函数
// 新增清理函数,在清理 ref 的时候调用该函数
return () => {
// 清理 ref
};
}}
/>
也就是在创建和卸载的时候调用不同的函数
useDeferredValue
新增第二个参数initialValue
,组件初次渲染时该值将作为初始值展示,返回的deferredValue
在会在后台进行重新渲染。 如果不传入初始值,useDeferredValue
在初次渲染时将不会延迟,因为它没有可以渲染的 value 的先前数据。这个情况的话就和优化之前是一样的了。
支持 HTML Metadata 标签
这个功能推出之后就不用自己去编写方法 or 使用第三方库来根据组件更新 metadata 了(仅对于简单的替换)。
jsx
function BlogPost({post}) {
return (
<article>
<h1>{post.title}</h1>
{/* 这些将会自动提升到 <head> 部分 */}
<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>
);
}
stylesheet 样式表
- 通过在组件内写入 link 标签加载样式表,可以实现将样式表和依赖它们的组件捆绑,更容易管理样式表的加载。
- 内置支持管理样式表加载顺序的优先级 (precedence)。
- 在 SSR 中表现的优化
- CSR:如果加载的多个组件使用同一个样式表,那么只加载一次
script 脚本
<script>
有两种异步加载方式,defer
和 async
,defer
要按照顺序执行,如果组件层级很深,按照顺序去渲染对应的 script 会比较困难,async
是任意顺序的去执行。 React 19 在支持组件内写 script 的同时,支持在依赖对应的 script 的组件内加载渲染,不需要手动去管理这些 script。并且它和样式表一样也有去重的功能,多个相同的 script 只会加载并执行一次。
资源预加载
react-dom
新增了多个(预)加载的 API
preinit
: 立即加载并执行期望的脚本preload
: 预加载期望使用的资源,比如样式表、字体或外部脚本prefetchDNS
: 允许提前查找期望从中加载资源的服务器的 IPpreconnect
: 帮助提前连接到一个期望从中加载资源的服务器preinitModule
: 预获取和评估 ESM 模块preloadModule
: 立即预获取期望使用的 ESM 模块
以上 API 将会转换到 html 中 head 对应的标签内
水合时兼容第三方脚本和扩展
- 忽略未预期的标签:在水合过程中,React 19会忽略 和 中的未预期标签,避免因为第三方脚本或浏览器扩展插入的内容而导致的 mismatch 错误。
- 保留样式表:即使因为 hydration mismatch 而需要重新渲染整个文档,React 19 也会保留由第三方脚本和浏览器扩展插入的样式表。
- 更好的错误处理:React 19 改进了错误处理机制,提供了更多的错误处理选项,帮助开发者更好地处理第三方脚本和浏览器扩展可能导致的问题。
对自定义元素(Custom Elements)的支持
在 React 19 之前,React 应用中使用自定义元素可能会遇到的问题:
- React 会将不认识的属性(props)当作 HTML 属性处理,而不是 DOM 元素的属性(attributes),这可能导致自定义元素的属性无法正确应用。
- 在服务器端渲染(SSR)时,React 处理自定义元素的方式可能与客户端渲染(CSR)不一致,导致渲染差异。
改进:
- 在服务器端渲染(SSR)时,React 会将传递给自定义元素的 props 是原始数据类型(string, number,)或者是 true 的时候渲染为 HTML 属性。对于非原始数据类型(如 object, symbol, function)或是 false 的props,React 19 会忽略它们,不渲染为 HTML 属性。
- 在客户端渲染时,React 会根据自定义元素实例上的属性来处理传递给自定义元素的 props。如果传递的 props 与自定义元素实例上的属性匹配,React 会将它们作为属性进行赋值。否则,React 会将它们作为HTML 属性进行赋值。