早在数月前,React 团队便预告了 React 19 的积极开发,并预计上半年发布。 4 月 25 日,React 终于发布了 v19 测试版。该版本主要面向各大库,以确保它们与 React 19 的兼容性。因此,建议开发者先升级至最新的稳定版 18.3.0,静待 React 19 的正式版发布。
React 19 带来了诸多新特性和改进,不仅提升了开发者的使用体验,还进一步优化了 React 应用的性能。为了让开发者能够平稳过渡到 React 19,React 团队特意准备了一份详尽的升级指南,详细列出了升级步骤和可能遇到的重大变化。
接下来,本文将剖析 React 18.3 与 React 19 的更新内容,并探索 React 19 的升级指南,助力更好地拥抱这一重大更新!
React 18.3 更新内容
React 18.3 相对于 18.2 增加了对废弃 API 的警告以及其他为 React 19 所需的更改。
- React
- 允许向 this.refs 写入以支持字符串 ref 的代码模式转换。
- 在 StrictMode 外部使用已废弃的 findDOMNode 时,将发出警告。
- 对使用已废弃的测试工具方法时发出警告。
- 在 StrictMode 外部使用已废弃的遗留 Context 时,将发出警告。
- 在 StrictMode 外部使用已废弃的字符串 ref 时,将发出警告。
- 对函数组件中使用已废弃的 defaultProps 发出警告。
- 当在组件或元素中展开 key 时,将发出警告。
- 从测试工具中使用 act 时,如果方式不当,将发出警告。
- React DOM
-
对使用已废弃的 unmountComponentAtNode 方法时发出警告。
-
对使用已废弃的 renderToStaticNodeStream 方法时发出警告。
React 19 更新内容
Actions
在React应用中,执行数据变更并据此更新状态是一个常见的需求。比如,用户提交表单以更改姓名时,我们会发起API请求并处理其响应。以往,我们不得不手动管理挂起状态、错误处理、乐观更新以及顺序请求等逻辑。
例如,可以使用useState来处理挂起和错误状态:
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const handleSubmit = async () => {
setIsPending(true);
const error = await updateName(name);
setIsPending(false);
if (error) {
setError(error);
return;
}
redirect("/path");
};
return (
setName(event.target.value)} />
Update
{error && {error}
}
);
}
在 React 19 中增加了对在转换中使用异步函数的支持,以自动处理挂起状态、错误、表单和乐观更新。
例如,可以使用useTransition来自动处理挂起状态:
function UpdateName({}) {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = async () => {
startTransition(async () => {
const error = await updateName(name);
if (error) {
setError(error);
return;
}
redirect("/path");
})
};
return (
setName(event.target.value)} />
Update
{error && {error}
}
);
}
异步转换会立刻将isPending状态设为true,发起异步请求,并在转换完成后将isPending设置为false。这样可以确保数据变化时,UI 仍然保持响应和交互性。
在 Actions 功能的基础上,React 19 还推出了useOptimistic Hook 来简化乐观更新的管理,以及 React.useActionState Hook来处理 Actions 的常见场景。同时,在 react-dom 中,新增了 Actions 来自动管理表单操作,并提供了useFormStatus 来支持表单中 Actions 的常见需求。
在 React 19 中,上面的例子可以简化为:
function ChangeName({ name, setName }) {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const error = await updateName(formData.get("name"));
if (error) {
return error;
}
redirect("/path");
}
);
return (
Update
{error && {error}
}
);
}
下面就来看看这些新的 Actions 功能的作用和使用方法。
全新 Hook:useActionState
为了让常见的 Action 用例更加简便,因此添加了一个名为useActionState的新 Hook:
const [error, submitAction, isPending] = useActionState(async (previousState, newName) => {
const error = await updateName(newName);
if (error) {
// 可以返回Action的任何结果,这里只返回错误信息。
return error;
}
// 处理成功的情况
});
useActionState接受一个函数(即“Action”),并返回一个可调用的包装 Action。这之所以能够工作是因为Actions是可组合的。当调用包装后的Action时,useActionState将返回Action的最后一个结果作为数据,并将Action的挂起状态作为pending返回。
React DOM: Actions
在 React 19 中,Actions 与 react-dom 的新 特性进行了深度整合。现在,可以将函数作为、和元素的action和formAction属性,以便自动使用 Actions 提交表单:
当的action成功执行时,React会自动为不受控组件重置表单状态。如果需要手动重置,React DOM API 提供了全新的requestFormReset方法。
React DOM 新 Hook:useFormStatus
在构建设计系统时,经常需要创建一些设计组件,这些组件需要能够访问其所在表单的状态信息,而不必通过层层传递props来实现。虽然通过Context也可以实现这一功能,但为了简化常见场景下的使用,React 19 引入了一个新的 Hook useFormStatus:
import { useFormStatus } from 'react-dom';
function DesignButton() {
const { pending } = useFormStatus();
return ;
}
useFormStatus可以读取父级的状态,就像该表单是一个Contextprovider 一样。
全新 Hook:useOptimistic
在数据变更操作中,一种常见的 UI 模式是在异步请求执行期间乐观地显示预期的最终状态。React 19 引入了新的useOptimistic Hook,以简化这一流程:
function ChangeName({currentName, onUpdateName}) {
const [optimisticName, setOptimisticName] = useOptimistic(currentName);
const submitAction = async formData => {
const newName = formData.get("name");
setOptimisticName(newName);
const updatedName = await updateName(newName);
onUpdateName(updatedName);
};
return (
Your name is: {optimisticName}
Change Name:
);
}
在这个例子中,useOptimistic Hook 允许在updateName请求还在进行时,立即将输入框的值(即newName)设置为optimisticName,从而乐观地更新UI。如果更新成功,则通过onUpdateName回调函数来确认状态的更改;如果更新失败或发生错误,可以通过setOptimisticName回滚到原始状态currentName。
全新 API:use
在React 19中,引入了一个全新的API——use,它允许在组件渲染时直接读取资源。
举个例子,可以使用use来读取一个Promise对象,而React将会自动挂起(Suspend)组件的渲染,直到该Promise解析完成:
import { use } from 'react';
function Comments({ commentsPromise }) {
// 使用use读取Promise,React会在Promise解析前挂起组件渲染
const comments = use(commentsPromise);
return (
{comments.map((comment) => (
{comment.text}
))}
);
}
function Page({ commentsPromise }) {
// 当Comments组件因use挂起时,这里会显示Suspense的fallback内容
return (
);
}
同样可以使用use来读取上下文(Context),从而实现在特定条件下(例如早期返回之后)按需读取上下文数据。
早期返回指的是在函数或方法中的某个点提前结束执行,并返回结果或退出,而不是继续执行剩余的代码。在函数内部,根据某些条件或逻辑,你可能会决定不需要继续执行后续的代码,此时可以使用return语句来立即退出函数。
import {use} from 'react';
import ThemeContext from './ThemeContext'
function Heading({children}) {
if (children == null) {
return null;
}
// 使用 useContext 在这里不会生效 ,因为存在早期返回。
const theme = use(ThemeContext);
return (
{children}
);
}
use API 的调用仅限于渲染阶段,与 React 的 Hook 类似。然而,与 Hook 不同的是,use API 允许在条件语句中灵活调用。展望未来,React 团队计划进一步扩展 use API 的功能,提供更多在渲染时消费资源的方式。
React 服务器组件
服务器组件
服务器组件是一种创新性的技术,它允许在打包前,在独立于客户端应用程序或服务器端渲染(SSR)服务器的环境中预先渲染组件。这个独立的环境即为React服务器组件中的“服务器”。服务器组件有两种运行模式:一种是在构建时(例如在持续集成服务器上)运行一次;另一种是针对每个请求,通过 Web 服务器进行实时运行。
React 19 全面集成了来自 Canary 频道的所有服务器组件特性。这意味着,那些带有服务器组件的库现在可以将 React 19 作为对等依赖项进行目标定位,并通过 react-server 导出条件,为支持全栈 React 架构的框架提供强大的支持。
服务器操作
服务器动作是一项强大功能,它允许客户端组件调用并执行在服务端的异步函数。
通过“use server”指令定义服务器操作后,框架将智能地创建一个指向服务端函数的引用,并安全地将该引用传递给客户端组件。当客户端组件需要调用这个函数时,React 会负责向服务器发送请求,执行相应的函数,并将结果返回给客户端。
服务器操作的创建非常灵活,既可以在服务器组件内部创建,并作为属性传递给客户端组件使用;也可以直接在客户端组件中导入并调用。这种设计使得服务器操作能够无缝集成到应用中,实现前后端数据的流畅交互。
功能改进
ref 作为属性
从 React 19 开始,可以将 ref 作为函数组件的参数进行访问:
function MyInput({placeholder, ref}) {
return
}
//...
新的函数组件将不再需要 forwardRef,React 团队将会发布一个代码转换工具来自动更新组件,以使用新的 ref 属性。在未来的版本中,将弃用并移除 forwardRef。
水合错误报告
React 19 在 react-dom 中对水合错误的报告进行了优化。过去,在开发模式下遇到水合不匹配时,系统往往只记录多个错误,而缺乏关于不匹配内容的具体信息。现在,引入了 diff 功能,使得客户端渲染与服务端渲染内容之间的差异一目了然。这一改进不仅提升了错误报告的清晰度,更有助于开发者迅速定位并修复水合相关问题,从而大幅提升开发效率。
现在只会记录一条包含不匹配内容差异的消息:
作为提供者
React 19 允许直接将 用作提供者,而无需使用传统的 写法:
const ThemeContext = createContext('');
function App({children}) {
return (
{children}
);
}
这种新的语法更加简洁直观。为了方便开发者升级现有代码,React 团队将发布一个代码转换工具,能够自动将现有的 转换为新的 提供者。未来版本中,将逐步弃用 ,以推动 React 社区向更加简化的语法过渡。
refs 清理函数
现在支持从 ref 回调函数中返回一个清理函数:
{
// 创建 ref
// 新增:返回一个清理函数,当元素从 DOM 中移除时重置 ref。
return () => {
// ref 的清理工作
};
}}
/>
当组件卸载时,React 将调用从 ref 回调函数中返回的清理函数。这适用于 DOM refs、类组件的 refs 以及 useImperativeHandle。
由于引入了 ref 清理函数的机制,现在 TypeScript 将拒绝从 ref 回调函数中返回除清理函数以外的任何内容。为了避免这个问题,我们通常建议避免使用隐式返回,比如将赋值操作放在花括号中,如下所示:
- 原来的写法:
(instance = current)} />
- 优化后的写法:
{ instance = current; }} />
这种改变是因为 TypeScript 无法判断原始代码中返回的是否应该是清理函数,还是无意中的隐式返回值。通过将赋值操作明确地包裹在花括号中,确保了 ref 回调中不会意外地返回任何值,除非有意为之。
为了自动化这种模式的转换,可以使用 no-implicit-ref-callback-return 规则进行代码转换。这将帮助你在升级 React 版本时更顺畅地处理 ref 相关的代码。
useDeferredValue 的初始值
React 19 为 useDeferredValue 引入了 initialValue 选项,该选项允许指定组件首次渲染时返回的值。
function Search({ deferredValue }) {
// 在组件首次渲染时,返回 initialValue 作为 value。
// 随后,useDeferredValue 会在后台计划一次重渲染,使用 deferredValue 作为新的 value。
const value = useDeferredValue(deferredValue, { initialValue: '' });
return (
);
}
使用 initialValue 可以确保组件在首次渲染时能够立即显示一个占位值,而无需等待 deferredValue 的异步计算完成。随后,当 deferredValue 准备好时,useDeferredValue 会触发组件的后台重渲染,以显示最新的值。这有助于提升应用的响应性和用户体验。
文档元数据支持
在HTML中,诸如、和等文档元数据标签通常放置在区域内。然而,在React应用中,决定哪些元数据适用于当前页面的组件可能并不直接位于渲染的位置,甚至React可能根本不直接渲染。过去,这些元数据标签需要手动通过effect或借助如react-helmet这样的库进行插入,且在服务器渲染React应用时需要特别小心处理。
React 19引入了原生支持在组件中渲染文档元数据标签的功能。这意味着可以直接在组件内部定义这些标签,React会自动将它们提升到文档的部分。这一改进确保了元数据标签能够在仅客户端应用、流式服务端渲染(SSR)以及服务器组件等场景下正常工作。
下面是一个简单的示例,展示了如何在React组件中定义并使用这些元数据标签:
function BlogPost({ post }) {
return (
{post.title}
{/* 直接在组件内部定义元数据标签 */}
{post.title}
Eee equals em-see-squared...
);
}
在这个BlogPost组件中,尽管、和标签被定义在了内部,但React会在渲染时自动将它们移至文档的区域。这种方式简化了元数据的管理,还增强了React应用在各种渲染模式下的兼容性和灵活性。
样式表支持
在Web开发中,样式表的管理至关重要,无论是通过外部链接()还是内嵌方式(...)引入,都需要在 DOM 中精准布局,以确保样式优先级得到妥善处理。然而,构建一个能够支持组件内部样式表组合的机制往往十分繁琐,因此开发者常常面临权衡:要么将样式远离其依赖的组件加载,牺牲组织性;要么依赖外部样式库,增加额外的复杂性。
React 19 针对这一挑战提供了内置支持,不仅简化了样式表的管理流程,还进一步增强了与客户端并发渲染和服务器端流式渲染的集成能力。通过指定样式表的优先级,React 将自动处理样式表在DOM中的插入顺序,确保在展示依赖于特定样式规则的内容之前,相关的样式表(无论外部还是内嵌)已经被加载并应用。
function ComponentOne() {
return (
{...}
)
}
function ComponentTwo() {
return (
{...}