React 19 新特性详解
React 19 是 React 框架的一次重要升级,带来了诸多令人兴奋的新特性。其中最引人注目的包括 React Compiler 自动记忆化、Actions 模式、新的乐观更新 Hook(useOptimistic)、use API、文档元数据支持等。这些特性大幅简化了开发者的日常工作,让代码更简洁、性能更优秀。本文将深入讲解 React 19 的核心新特性,并提供迁移指南。
一、React Compiler 自动记忆化
1.1 背景:手动记忆化的痛点
在 React 19 之前,为了避免不必要的重渲染,开发者需要手动使用 useMemo、useCallback 和 React.memo 来记忆化值、函数和组件。这带来了几个问题:
- 代码冗长,模板代码大量增加
- 依赖数组容易写错,导致 bug
- 过度使用 memo 反而可能降低性能
- 新手开发者难以掌握正确的使用时机
1.2 React Compiler 解决方案
React Compiler 是一个编译时优化工具,它会自动分析组件代码,在需要的地方自动插入记忆化逻辑。这意味着开发者可以专注于业务逻辑,而无需手动管理 memo:
// React 18:需要手动 memo
import { useMemo, useCallback } from 'react';
function ProductList({ products, filterText }) {
// 手动 memo 计算
const filteredProducts = useMemo(() => {
return products.filter(p =>
p.name.toLowerCase().includes(filterText.toLowerCase())
);
}, [products, filterText]);
// 手动 memo 函数
const handleAddToCart = useCallback((product) => {
addToCart(product);
}, []);
return (
<ul>
{filteredProducts.map(product => (
<ProductItem
key={product.id}
product={product}
onAddToCart={handleAddToCart}
/>
))}
</ul>
);
}
// React 19:无需手动 memo,编译器自动处理
function ProductList({ products, filterText }) {
// 编译器会自动 memo 这个计算
const filteredProducts = products.filter(p =>
p.name.toLowerCase().includes(filterText.toLowerCase())
);
// 编译器会自动 memo 这个函数
const handleAddToCart = (product) => {
addToCart(product);
};
return (
<ul>
{filteredProducts.map(product => (
<ProductItem
key={product.id}
product={product}
onAddToCart={handleAddToCart}
/>
))}
</ul>
);
}
1.3 启用 React Compiler
// babel 配置
// babel.config.js
module.exports = {
presets: [
['@babel/preset-react', { runtime: 'automatic' }]
],
plugins: [
// 启用 React Compiler
['babel-plugin-react-compiler']
]
};
// Vite 配置
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: [
['babel-plugin-react-compiler']
]
}
})
]
});
1.4 Compiler 的规则
React Compiler 对代码有一些要求,违反这些规则可能导致编译失败或运行时错误:
- 不要在渲染过程中直接修改(mutate)状态或 props
- 组件必须是纯函数(相同的输入产生相同的输出)
- ref 的
.current只能在事件处理函数或 effect 中修改
// 错误:在渲染过程中修改 props
function BadComponent({ items }) {
items.push(1); // 不要这样做
return <div>{items.length}</div>;
}
// 正确:创建新数组
function GoodComponent({ items }) {
const newItems = [...items, 1];
return <div>{newItems.length}</div>;
}
二、Actions 与 useActionState
React 19 引入了 Actions 概念,统一了异步操作(如提交表单、数据获取)的处理方式。useActionState 是为此设计的核心 Hook。
2.1 旧方式 vs 新方式
// React 18:手动管理异步状态
import { useState } from 'react';
function FeedbackForm() {
const [message, setMessage] = useState('');
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
async function handleSubmit(event) {
event.preventDefault();
setIsPending(true);
setError(null);
try {
const response = await submitFeedback(message);
if (response.error) {
setError(response.error);
} else {
setMessage('');
alert('提交成功!');
}
} catch (err) {
setError(err.message);
} finally {
setIsPending(false);
}
}
return (
<form onSubmit={handleSubmit}>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
{error && <p className="error">{error}</p>}
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>
</form>
);
}
// React 19:使用 useActionState 简化
import { useActionState } from 'react';
function FeedbackForm() {
const [state, formAction, isPending] = useActionState(
async (previousState, formData) => {
const message = formData.get('message');
try {
const response = await submitFeedback(message);
if (response.error) {
return { error: response.error };
}
return { success: true };
} catch (err) {
return { error: err.message };
}
},
{ error: null } // 初始状态
);
return (
<form action={formAction}>
<textarea name="message" />
{state?.error && <p className="error">{state.error}</p>}
{state?.success && <p className="success">提交成功!</p>}
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>
</form>
);
}
2.2 与表单原生集成
React 19 让 <form> 元素可以直接使用 action 属性,无需 onSubmit:
import { useActionState } from 'react';
function LoginForm() {
const [state, loginAction, isPending] = useActionState(
async (prev, formData) => {
const username = formData.get('username');
const password = formData.get('password');
const result = await api.login(username, password);
if (!result.success) {
return { error: result.message };
}
// 登录成功后跳转
window.location.href = '/dashboard';
return null;
},
null
);
return (
<form action={loginAction}>
<input type="text" name="username" required />
<input type="password" name="password" required />
<button type="submit" disabled={isPending}>
{isPending ? '登录中...' : '登录'}
</button>
{state?.error && <p>{state.error}</p>}
</form>
);
}
三、useOptimistic 乐观更新
useOptimistic 是 React 19 的另一个重要 Hook,用于实现乐观更新(Optimistic Updates)。乐观更新指的是在异步操作完成前,先假设操作成功并更新 UI,如果操作失败再回滚。这种模式能给用户更流畅的体验。
3.1 基本用法
import { useOptimistic, useState } from 'react';
function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: '学习 React 19', completed: false }
]);
// useOptimistic 提供乐观更新状态
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [
...state,
{ id: 'temp-' + Date.now(), text: newTodo, completed: false, pending: true }
]
);
async function addTodo(formData) {
const text = formData.get('todo');
// 立即显示乐观更新(UI 立即变化)
addOptimisticTodo(text);
// 异步发送请求
try {
const result = await api.addTodo(text);
// 请求成功后用真实数据替换乐观数据
setTodos(prev => [...prev, result]);
} catch (err) {
// 请求失败,useOptimistic 会自动回滚
console.error('添加失败:', err);
}
}
return (
<div>
<form action={addTodo}>
<input name="todo" placeholder="添加待办事项" />
<button type="submit">添加</button>
</form>
<ul>
{optimisticTodos.map(todo => (
<li
key={todo.id}
style={{ opacity: todo.pending ? 0.5 : 1 }}
>
{todo.text}
{todo.pending && <span> (添加中...)</span>}
</li>
))}
</ul>
</div>
);
}
3.2 点赞功能示例
import { useOptimistic, useState } from 'react';
function LikeButton({ initialCount, postId }) {
const [likeCount, setLikeCount] = useState(initialCount);
// 乐观更新:点击立即 +1,失败回滚
const [optimisticCount, addOptimistic] = useOptimistic(
likeCount,
(state, delta) => state + delta
);
async function handleLike() {
addOptimistic(1); // 立即显示 +1
try {
const result = await api.like(postId);
setLikeCount(result.count); // 用真实数据替换
} catch (err) {
// 自动回滚到 likeCount
console.error('点赞失败:', err);
}
}
return (
<button onClick={handleLike}>
❤ {optimisticCount}
</button>
);
}
四、use API
use 是 React 19 新增的一个 API(注意它不是 Hook,而是一个普通函数),用于读取 Promise 或 Context 的值。与 Hook 不同,use 可以在条件语句中调用。
4.1 读取 Promise
import { use, Suspense } from 'react';
// 异步数据获取函数
async function fetchUser(userId) {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
function UserProfile({ userPromise }) {
// use 会暂停组件渲染,直到 Promise resolve
// 需要在 Suspense 边界内使用
const user = use(userPromise);
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
<p>注册时间:{user.createdAt}</p>
</div>
);
}
// 父组件
function App({ userId }) {
// 在父组件创建 Promise
const userPromise = fetchUser(userId);
return (
<Suspense fallback={<div>加载用户信息...</div>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}
4.2 读取 Context(可在条件语句中调用)
import { use, createContext } from 'react';
const ThemeContext = createContext('light');
const UserContext = createContext(null);
function Header({ isAuthenticated }) {
// use 可以在条件语句中调用(Hook 不行)
const theme = use(ThemeContext);
let greeting = '游客';
if (isAuthenticated) {
// 在 if 中使用 use 读取 Context
const user = use(UserContext);
greeting = `你好,${user.name}`;
}
return (
<header className={`header header-${theme}`}>
{greeting}
</header>
);
}
4.3 结合 Suspense 处理加载状态
import { use, Suspense } from 'react';
// 多个异步请求并行处理
async function fetchProfile() {
const [user, posts, friends] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/friends').then(r => r.json())
]);
return { user, posts, friends };
}
function ProfileContent({ profilePromise }) {
const { user, posts, friends } = use(profilePromise);
return (
<div>
<h1>{user.name} 的主页</h1>
<section>
<h2>文章 ({posts.length})</h2>
{posts.map(post => <Article key={post.id} post={post} />)}
</section>
<section>
<h2>好友 ({friends.length})</h2>
{friends.map(friend => <UserCard key={friend.id} user={friend} />)}
</section>
</div>
);
}
function ProfilePage() {
return (
<Suspense fallback={<div className="loading">加载中...</div>}>
<ProfileContent profilePromise={fetchProfile()} />
</Suspense>
);
}
五、文档元数据支持
React 19 原生支持在组件中渲染 <title>、<meta>、<link> 等元数据标签,React 会自动将它们提升到 <head> 中。这消除了对 react-helmet 等第三方库的需求。
// React 19:直接在组件中写元数据
function BlogPost({ post }) {
return (
<article>
{/* 这些标签会被自动提升到 <head> */}
<title>{post.title} - 我的博客</title>
<meta name="description" content={post.excerpt} />
<meta name="keywords" content={post.tags.join(', ')} />
<meta property="og:title" content={post.title} />
<meta property="og:description" content={post.excerpt} />
<meta property="og:image" content={post.coverImage} />
<link rel="canonical" href={`https://example.com/blog/${post.slug}`} />
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
// 还可以用于动态样式表和脚本
function PageWithCustomStyles() {
return (
<div>
<link rel="stylesheet" href="/page-specific.css" />
<style>{`
.hero { background: linear-gradient(135deg, #667eea, #764ba2); }
`}</style>
<section className="hero">欢迎</section>
</div>
);
}
六、ref 作为 prop 传递
React 19 中,ref 可以直接作为普通 prop 传递给函数组件,无需再使用 forwardRef。
// React 18:需要使用 forwardRef
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
return <input ref={ref} {...props} />;
});
// 使用
function App() {
const inputRef = useRef(null);
return <MyInput ref={inputRef} />;
}
// React 19:直接通过 props 传递 ref
function MyInput({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
// 使用方式不变
function App() {
const inputRef = useRef(null);
return <MyInput ref={inputRef} placeholder="请输入" />;
}
对于需要暴露自定义方法给 ref 的情况,使用 useImperativeHandle:
import { useRef, useImperativeHandle } from 'react';
function CustomInput({ ref }) {
const inputRef = useRef(null);
// 暴露自定义方法
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => {
if (inputRef.current) inputRef.current.value = '';
},
getValue: () => inputRef.current?.value
}));
return <input ref={inputRef} />;
}
function App() {
const inputRef = useRef(null);
function handleFocus() {
inputRef.current?.focus();
}
function handleClear() {
inputRef.current?.clear();
}
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={handleFocus}>聚焦</button>
<button onClick={handleClear}>清空</button>
</div>
);
}
七、异步脚本支持
React 19 改进了对异步脚本(<script async>)的处理。你可以在组件内部渲染 <script> 标签,React 会自动去重并正确处理加载顺序:
// React 19:在组件中使用异步脚本
function MapWidget() {
return (
<div>
{/* 即使多个组件都渲染这个脚本,React 也只会加载一次 */}
<script async src="https://maps.googleapis.com/maps/api/js?key=API_KEY" />
<div id="map" style={{ width: '100%', height: '400px' }}>
地图加载中...
</div>
</div>
);
}
// 内联脚本
function Analytics() {
return (
<script>{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_TRACKING_ID');
`}</script>
);
}
八、其他改进
8.1 Context 简化
// React 18:需要 Provider
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Page />
</ThemeContext.Provider>
);
}
// React 19:可以直接使用 Context 作为 Provider
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext value="dark">
<Page />
</ThemeContext>
);
}
8.2 ref 回调函数清理
// React 19:ref 回调可以返回清理函数
function InputWithCleanup() {
return (
<input
ref={(element) => {
// 设置逻辑
element?.focus();
// 返回清理函数(React 19 新增)
return () => {
console.log('元素被卸载,执行清理');
};
}}
/>
);
}
8.3 错误信息改进
React 19 提供了更清晰的错误信息,并附带原始错误堆栈,方便调试:
// 自定义错误处理
function RootErrorBoundary({ children }) {
const [error, setError] = useState(null);
useEffect(() => {
const handler = (event) => {
setError(event.error);
event.preventDefault();
};
window.addEventListener('error', handler);
return () => window.removeEventListener('error', handler);
}, []);
if (error) {
return (
<div className="error-boundary">
<h2>出错了</h2>
<p>{error.message}</p>
<details>
<summary>错误堆栈</summary>
<pre>{error.stack}</pre>
</details>
<button onClick={() => setError(null)}>重试</button>
</div>
);
}
return children;
}
九、迁移指南
9.1 升级到 React 19
# 使用 npm
npm install react@19 react-dom@19
# 使用 yarn
yarn add react@19 react-dom@19
# 使用 pnpm
pnpm add react@19 react-dom@19
9.2 代码迁移清单
- 移除 forwardRef:函数组件可以直接接收 ref 作为 prop
- 移除手动 memo:启用 React Compiler 后,可以移除大部分
useMemo、useCallback、React.memo - 使用 Context 替代 Context.Provider:可以直接使用
<ThemeContext value={...}> - 移除 react-helmet:使用原生元数据标签支持
- 使用 useActionState 替代手动异步状态管理
9.3 渐进式迁移示例
// 迁移前:React 18 写法
import { forwardRef, useMemo, useCallback, useState } from 'react';
const SearchInput = forwardRef(function SearchInput({ value, onChange }, ref) {
return <input ref={ref} value={value} onChange={onChange} />;
});
function SearchBar({ items }) {
const [query, setQuery] = useState('');
const inputRef = useRef(null);
const filteredItems = useMemo(() => {
return items.filter(item =>
item.name.includes(query)
);
}, [items, query]);
const handleChange = useCallback((e) => {
setQuery(e.target.value);
}, []);
return (
<div>
<SearchInput ref={inputRef} value={query} onChange={handleChange} />
<ul>
{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
// 迁移后:React 19 写法(启用 Compiler)
function SearchInput({ ref, value, onChange }) {
return <input ref={ref} value={value} onChange={onChange} />;
}
function SearchBar({ items }) {
const [query, setQuery] = useState('');
const inputRef = useRef(null);
// 无需 useMemo,编译器自动优化
const filteredItems = items.filter(item =>
item.name.includes(query)
);
// 无需 useCallback,编译器自动优化
const handleChange = (e) => {
setQuery(e.target.value);
};
return (
<div>
<SearchInput ref={inputRef} value={query} onChange={handleChange} />
<ul>
{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
十、总结
React 19 带来的新特性显著改善了开发体验:
- React Compiler:自动记忆化,消除手动 memo 的负担
- Actions 与 useActionState:统一异步操作处理,简化表单提交
- useOptimistic:内置乐观更新支持,提升用户体验
- use API:灵活读取 Promise 和 Context,可在条件语句中使用
- 文档元数据支持:原生支持 title、meta 等标签,无需第三方库
- ref 作为 prop:移除 forwardRef,简化组件 API
- 异步脚本支持:在组件中安全使用 script 标签
这些特性让 React 代码更加简洁、直观,同时保持了优秀的性能。建议在新项目中直接使用 React 19,现有项目可以参考迁移指南逐步升级。React Compiler 虽然是可选的,但一旦启用,将大幅减少模板代码,让开发者真正专注于业务逻辑的实现。