React 19 已于 2024 年 12 月 5 日正式发布,并在 npm 上稳定可用。这一版本带来了诸多令人振奋的新特性和改进,旨在简化开发流程、提升性能,并增强与现代开发需求的兼容性。本文将详细介绍 React 19 的主要新特性、改进、状态管理的变化以及如何顺利升级,帮助开发者充分利用这些新功能构建高效、响应迅速的应用。
目录
主要新特性
Actions
概述
在 React 应用中,常见的操作是执行数据变更并随后更新状态。例如,当用户提交表单更改姓名时,你需要发起 API 请求,并处理响应。在之前的版本中,开发者需要手动处理待处理状态、错误、乐观更新和顺序请求等。然而,React 19 引入了 Actions,通过简化这些流程,自动管理这些状态更新,大大提升了开发效率。
示例
升级前:手动管理待处理和错误状态
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 (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
升级后:使用 Actions 简化状态管理
function UpdateName() {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = () => {
startTransition(async () => {
const error = await updateName(name);
if (error) {
setError(error);
return;
}
redirect("/path");
});
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
优势
- 待处理状态:Actions 自动管理请求的开始和结束状态,减少手动设置状态的繁琐。
- 乐观更新:通过
useOptimistic
钩子,用户可以在请求进行时立即看到更新结果,提高响应性。 - 错误处理:自动处理错误,支持显示错误边界并回滚乐观更新。
- 表单支持:
<form>
元素支持传递函数到action
和formAction
属性,自动提交和重置表单。
新钩子(Hooks)
React 19 引入了多个新钩子,进一步简化状态管理和数据处理。
useActionState
功能:简化 Actions 的使用,返回错误、提交动作和待处理状态。
示例
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const error = await updateName(formData.get("name"));
if (error) {
return error;
}
redirect("/path");
return null;
},
null,
);
useOptimistic
功能:支持乐观更新,在异步请求进行时立即显示最终状态。
示例
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 (
<form action={submitAction}>
<p>Your name is: {optimisticName}</p>
<p>
<label>Change Name:</label>
<input
type="text"
name="name"
disabled={currentName !== optimisticName}
/>
</p>
</form>
);
}
use
功能:在渲染时读取资源(如 Promise),并支持 Suspense。
示例
import { use } from 'react';
function Comments({ commentsPromise }) {
const comments = use(commentsPromise);
return comments.map(comment => <p key={comment.id}>{comment}</p>);
}
function Page({ commentsPromise }) {
return (
<Suspense fallback={<div>Loading...</div>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
);
}
注意:use
不能用于在渲染过程中创建的 Promise,需确保 Promise 来自 Suspense 兼容的库或框架。
React DOM Static APIs
prerender 和 prerenderToNodeStream
功能:改进静态 HTML 生成,支持数据加载等待,适用于流式渲染环境。
示例
import { prerender } from 'react-dom/static';
async function handler(request) {
const { prelude } = await prerender(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}
React Server Components
Server Components
功能:允许在独立于客户端应用或 SSR 服务器的环境中预渲染组件,适用于全栈 React 架构。
特点
- 运行环境独立于客户端或 SSR 服务器。
- 可在构建时或每个请求时执行。
Server Actions
功能:允许客户端组件调用在服务器上执行的异步函数,通过 "use server"
指令实现。
示例
// Server Component
'use server';
export async function fetchData() {
// 服务器端逻辑
}
// Client Component
import { fetchData } from './ServerComponent';
function ClientComponent() {
const handleClick = async () => {
const data = await fetchData();
// 处理数据
};
return <button onClick={handleClick}>Fetch Data</button>;
}
改进与优化
ref 作为属性
功能:函数组件现在可以直接接收 ref
作为属性,无需使用 forwardRef
。
示例
function MyInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
// 使用方式
<MyInput ref={inputRef} />
优势:简化函数组件中 ref
的传递,未来将逐步弃用 forwardRef
。
Hydration 错误的差异显示
改进:在 hydration 过程中,如果服务器和客户端渲染的内容不匹配,React 19 提供了更详细的差异信息,帮助开发者快速定位问题。
示例错误信息
Uncaught Error: Hydration failed because the server rendered HTML didn’t match the client. As a result this tree will be regenerated on the client. This can happen if an SSR-ed Client Component used:
- A server/client branch if (typeof window !== 'undefined').
- Variable input such as Date.now() or Math.random() which changes each time it’s called.
- Date formatting in a user’s locale which doesn’t match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
- ...etc.
Context 作为提供者
功能:可以直接使用 <Context>
代替 <Context.Provider>
,简化上下文提供者的使用。
示例
const ThemeContext = createContext('');
function App({ children }) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
优势:减少代码冗余,未来将逐步弃用 <Context.Provider>
。
ref 回调的清理函数
功能:支持在 ref
回调中返回清理函数,确保在组件卸载时正确清理引用。
示例
<input
ref={(ref) => {
// 创建引用
// 返回清理函数
return () => {
// 清理逻辑
};
}}
/>
注意:返回清理函数后,React 将跳过调用 ref
函数时传递 null
的步骤。
useDeferredValue 初始值
功能:为 useDeferredValue
添加 initialValue
选项,允许在初始渲染时设置默认值,并在后台调度重新渲染。
示例
function Search({ deferredValue }) {
const value = useDeferredValue(deferredValue, '');
return (
<Results query={value} />
);
}
文档元数据支持
功能:原生支持在组件中渲染 <title>
、<link>
和 <meta>
标签,自动提升到文档 <head>
部分,简化元数据管理。
示例
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>
);
}
样式表支持
功能:内置对样式表(外部和内联)的支持,确保样式按优先级正确加载,避免样式冲突和重复加载。
示例
function ComponentOne() {
return (
<Suspense fallback="loading...">
<link rel="stylesheet" href="foo.css" precedence="default" />
<link rel="stylesheet" href="bar.css" precedence="high" />
<article className="foo-class bar-class">
{...}
</article>
</Suspense>
);
}
function ComponentTwo() {
return (
<div>
<p>{...}</p>
<link rel="stylesheet" href="baz.css" precedence="default" /> {/* 将插入到 foo.css 和 bar.css 之间 */}
</div>
);
}
function App() {
return (
<>
<ComponentOne />
...
<ComponentOne /> {/* 不会导致重复加载样式表 */}
</>
);
}
异步脚本支持
功能:改进对异步脚本的支持,允许在组件树的任意位置渲染 <script async>
,并自动去重,确保脚本只加载一次。
示例
function MyComponent() {
return (
<div>
<script async src="https://example.com/script.js" />
Hello World
</div>
);
}
function App() {
return (
<html>
<body>
<MyComponent />
...
<MyComponent /> {/* 不会导致重复加载脚本 */}
</body>
</html>
);
}
资源预加载支持
功能:新增 prefetchDNS
、preconnect
、preload
和 preinit
API,优化资源加载性能,提升页面加载速度。
示例
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom';
function MyComponent() {
preinit('https://example.com/script.js', { as: 'script' }); // 立即加载并执行脚本
preload('https://example.com/font.woff', { as: 'font' }); // 预加载字体
preload('https://example.com/stylesheet.css', { as: 'style' }); // 预加载样式表
prefetchDNS('https://example.com'); // 预取 DNS
preconnect('https://example.com'); // 预连接
}
第三方脚本和扩展的兼容性
改进:在 hydration 过程中,React 19 更好地处理第三方脚本和浏览器扩展插入的内容,避免因内容不匹配导致的错误。
更好的错误报告
改进:优化错误处理机制,减少错误日志的重复,提供更清晰的错误信息,并新增 onCaughtError
和 onUncaughtError
等根选项。
示例
升级前的错误日志
Uncaught Error: hit
at Throws
at renderWithHooks
…
Uncaught Error: hit <-- 重复
at Throws
at renderWithHooks
…
升级后的错误日志
Error: hit
at Throws
at renderWithHooks
…
The above error occurred in the Throws component:
at Throws
at ErrorBoundary
at App
自定义元素支持
功能:完全支持自定义元素,确保在客户端和 SSR 环境下属性和属性的正确处理,提高与自定义元素的兼容性。
示例
const ThemeContext = createContext('');
function App({ children }) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
注意:在服务器端渲染时,基于属性类型的不同,决定是将其渲染为属性还是忽略。
状态管理与 React Server Components
React 19 的 Server Components 与 Next.js 的 Server Components 有何相似与不同?
相似之处
- 服务器端渲染(SSR):
- React Server Components 和 Next.js 的 Server Components 都支持在服务器端渲染组件。这意味着可以在服务器上预先渲染部分 UI,减少客户端的渲染负担,提高性能。
- 数据获取优化:
- 两者都允许在服务器端获取数据,并将其传递给组件进行渲染,从而优化数据加载和页面响应速度。
不同之处
-
集成程度:
- Next.js 是一个全功能的框架,集成了 React Server Components 以及路由、API 路由、静态生成(SSG)等多种功能,提供了一套完整的开发体验。
- React Server Components 是 React 本身的一个特性,可以在任何使用 React 的项目中使用,而不仅限于 Next.js。它提供了更灵活的组件渲染方式,但需要开发者自行集成和配置。
-
使用方式:
- Next.js 的 Server Components 与其页面和 API 路由紧密结合,开发者可以通过文件系统路由轻松管理服务器组件。
- React Server Components 在 React 19 中更加通用,适用于各种项目架构,开发者需要手动处理服务器和客户端组件的分离与通信。
React 19 中是否仍需使用第三方状态管理库(如 Zustand)?
React 19 的新特性对状态管理的影响
React 19 引入了 Actions 和 useActionState 等新特性,这些特性旨在简化数据变更和状态管理的流程,自动处理待处理状态、错误处理、乐观更新等。这些内置功能确实在一定程度上 简化了状态管理,减少了手动管理状态的代码量。
仍需使用第三方状态管理库的原因
尽管 React 19 提供了更强大的内置状态管理工具,但在以下情况下,第三方状态管理库如 Zustand 仍然具有不可替代的优势:
-
复杂的全局状态管理:
- 对于大型应用,需要管理复杂的全局状态,第三方库如 Zustand 提供了更简洁和高效的 API,能够更好地处理复杂的状态逻辑。
-
持久化和中间件:
- Zustand 支持状态持久化、时间旅行、状态快照等高级功能,这些功能在 React 19 的内置特性中尚未完全覆盖。
-
跨组件共享状态:
- 虽然 React 的 Context API 可以实现跨组件共享状态,但 Zustand 提供了更为简单和高效的方式来管理和访问共享状态,减少了样板代码。
-
性能优化:
- Zustand 通过选择性订阅和最小化重新渲染,能够在性能上提供额外的优化,特别是在高频更新的场景中表现更佳。
React 19 是否可以省略一些第三方库的使用?
React 19 的更新确实在某些方面可以减少对第三方库的依赖,但这取决于具体的应用需求:
-
简单的状态管理:
- 对于状态逻辑较为简单的小型应用,React 19 的内置特性如 Actions 和 useActionState 可以满足需求,减少对 Zustand 等库的依赖。
-
表单处理:
- React 19 对表单的支持更加完善,提供了自动提交和重置表单的功能,可能减少对像 Formik 这样的表单管理库的需求。
-
数据获取和缓存:
- 新的 use 和 Suspense 特性简化了 数据获取和缓存逻辑,在某些情况下可以减少对像 React Query 这样的数据管理库的依赖。
总结
React 19 通过引入 Actions、useActionState、React Server Components 等新特性,确实在简化状态管理和服务器端渲染方面迈出了重要的一步。这些内置功能能够减少对某些第三方库的依赖,尤其是在处理简单状态和数据获取时。
然而,对于复杂的全局状态管理、高级功能需求(如持久化、中间件)以及性能优化等方面,第三方状态管理库如 Zustand 仍然是非常有价值的工具。它们提供了 React 19 内置特性尚未覆盖的功能,能够更好地满足大型应用和复杂场景的需求。
因此,是否需要使用第三方状态管理库,应根据具体项目的需求和复杂性来决定。对于追求简洁和高效的状态管理,React 19 提供的内置工具已经能够满足很多需求,但在更复杂的场景下,第三方库仍然不可或缺。
升级指南
升级到 React 19 涉及一些关键步骤和注意事项。以下是详细的升级指南,帮助你顺利过渡到最新版本。
安装 React 19
注意:新 JSX Transform 现在是 必需的
React 19 引入了新的 JSX 转换,需要确保项目中启用了新的 JSX 转换以利用 ref
作为 prop 和其他性能改进。如果未启用新的 JSX 转换,将会看到以下警告:
Your app (or one of its dependencies) is using an outdated JSX transform. Update to the modern JSX transform for faster performance: https://react.dev/link/new-jsx-transform
安装最新版本的 React 和 React DOM
使用 npm:
npm install --save-exact react@^19.0.0 react-dom@^19.0.0
使用 Yarn:
yarn add --exact react@^19.0.0 react-dom@^19.0.0
如果使用 TypeScript,还需要更新类型定义
使用 npm:
npm install --save-exact @types/react@^19.0.0 @types/react-dom@^19.0.0
使用 Yarn:
yarn add --exact @types/react@^19.0.0 @types/react-dom@^19.0.0
使用 Codemods 进行代码迁移
为了简化升级过程,React 团队与 codemod.com 合作,发布了一系列 codemods,能够自动更新代码以适应 React 19 的新 API 和模式。
运行所有 React 19 的 codemods
使用以下命令运行 React 19 的迁移脚本:
npx codemod@latest react/19/migration-recipe
这将运行 react-codemod
仓 库中的以下 codemods:
replace-reactdom-render
replace-string-ref
replace-act-import
replace-use-form-state
prop-types-typescript
注意:这不包括 TypeScript 的更改,详见下文。
查看所有可用的 codemods
请访问 react-codemod 仓库 获取所有可用的 codemod 列表。
处理破坏性变更
React 19 引入了一些破坏性变更,以下是主要变更及其处理方法:
渲染中的错误不再重新抛出
改进:减少错误日志的重复,错误处理方式有所变化。
升级前:
在 DEV 模式下,渲染过程中抛出的错误会被捕获并重新抛出,同时记录到 console.error
,导致错误日志重复。
升级后:
- 未捕获的错误:报告给
window.reportError
。 - 捕获的错误:仅记录到
console.error
。
解决方法:
如果你的生产环境错误报告依赖于错误被重新抛出,可能需要更新错误处理逻辑。你可以在创建根时添加自定义错误处理:
const root = createRoot(container, {
onUncaughtError: (error, errorInfo) => {
// 记录错误报告
},
onCaughtError: (error, errorInfo) => {
// 记录错误报告
}
});
移除已弃用的 React API
1. 移除函数组件的 propTypes 和 defaultProps
- PropTypes:React 19 移除了 React 包中的 propTypes 检查,建议迁移到 TypeScript 或其他类型检查方案。
升级前:
import PropTypes from 'prop-types';
function Heading({ text }) {
return <h1>{text}</h1>;
}
Heading.propTypes = {
text: PropTypes.string,
};
Heading.defaultProps = {
text: 'Hello, world!',
};
升级后:
interface Props {
text?: string;
}
function Heading({ text = 'Hello, world!' }: Props) {
return <h1>{text}</h1>;
}
使用 Codemod 迁移 propTypes 到 TypeScript
npx codemod@latest react/prop-types-typescript
2. 移除旧版 Context API(contextTypes 和 getChildContext)
旧版 Context 仅在类组件中使用,React 19 移除了这些 API。
升级前:
import PropTypes from 'prop-types';
class Parent extends React.Component {
static childContextTypes = {
foo: PropTypes.string.isRequired,
};
getChildContext() {
return { foo: 'bar' };
}
render() {
return <Child />;
}
}
class Child extends React.Component {
static contextTypes = {
foo: PropTypes.string.isRequired,
};
render() {
return <div>{this.context.foo}</div>;
}
}
升级后:
const FooContext = React.createContext();
class Parent extends React.Component {
render() {
return (
<FooContext.Provider value='bar'>
<Child />
</FooContext.Provider>
);
}
}
class Child extends React.Component {
static contextType = FooContext;
render() {
return <div>{this.context}</div>;
}
}
3. 移除字符串 refs
字符串 refs 已被弃用,React 19 移除了对字符串 refs 的支持,建议迁移到回调 refs。
升级前:
class MyComponent extends React.Component {
componentDidMount() {
this.refs.input.focus();
}
render() {
return <input ref='input' />;
}
}
升级后:
class MyComponent extends React.Component {
componentDidMount() {
this.input.focus();
}
render() {
return <input ref={input => this.input = input} />;
}
}
使用 Codemod 迁移字符串 refs 到回调 refs
npx codemod@latest react/19/replace-string-ref
4. 移除模块模式工厂和 React.createFactory
这些功能在现代 React 中已很少使用,React 19 移除了对它们的支持,建议使用 JSX 代替。
升级前:
import { createFactory } from 'react';
const button = createFactory('button');
升级 后:
const button = <button />;
5. 移除 react-test-renderer/shallow
React 19 移除了 react-test-renderer/shallow