2024年12月,React 19 正式发布,至今已过去大半年。尽管目前多数项目仍在使用 React 18,但我们可以通过官方文档和 GitHub 了解其带来的关键新特性和破坏性变更。以下是我总结的一些重要更新。
React相关文档也可参考这个地址:传送门
1、Optimistic (直译为:乐观)
React19新加入了一个概念,叫做乐观 。对应的API为useOptimistic。乐观更新是一种 UI 设计模式,其核心思想是:
"先相信操作会成功,提前更新 UI;如果失败了,再回滚到之前的状态。 这与传统的"悲观更新"(先等待服务器响应,成功后再更新 UI)形成对比。
为什么需要乐观更新?
- 提升用户体验:用户点击按钮后,UI 立即响应,无需等待网络延迟。
- 感觉更快:即使网络慢,用户也能看到操作"已生效",减少等待焦虑。
- 现代应用的标准做法:如社交媒体点赞、评论删除等,都采用此模式。
示例
js
import { useOptimistic, useState } from "react";
import { postComment as apiPostComment } from "../utils";
const apiPostComment = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('comment success')
}, 1000)
})
}
// 评论项组件
function CommentItem({ comment }) {
return (
<div className="border p-3 mb-2 rounded bg-white shadow-sm">
<p className="text-gray-800">{comment.text}</p>
{comment.isPending && (
<small className="text-blue-500 mt-1 block">正在提交...</small>
)}
</div>
);
}
// 评论区域组件
function CommentSection({ commentList, onAddComment }) {
const [pendingCommentId, setPendingCommentId] = useState(0);
// 乐观更新:立即显示待定评论
const [displayedComments, addPendingComment] = useOptimistic(
commentList,
(state, newCommentText) => {
const id = `pending-${pendingCommentId}`;
return [
...state,
{
id,
text: newCommentText,
isPending: true,
},
];
}
);
// 表单提交处理器
async function handleAddComment(formData) {
const content = formData.get("commentContent");
if (!content?.trim()) return;
// 生成本次提交的临时 ID
const currentPendingId = pendingCommentId;
setPendingCommentId((prev) => prev + 1);
// 1. 立即乐观更新 UI
addPendingComment(content);
try {
// 2. 发起真实请求
const result = await onAddComment(formData, `pending-${currentPendingId}`);
if (result.error) {
throw new Error(result.error.message);
}
} catch (error) {
console.error("评论提交失败:", error.message);
// 注意:useOptimistic 不会自动回滚,需配合其他状态管理
}
}
return (
<div className="comment-container">
{/* 渲染当前显示的评论(含乐观更新) */}
{displayedComments.length === 0 ? (
<p className="text-gray-500 italic">暂无评论</p>
) : (
displayedComments.map((comment) => (
<CommentItem key={comment.id} comment={comment} />
))
)}
{/* 添加评论表单 */}
<form onSubmit={handleAddComment} className="mt-4 space-y-3">
<textarea
name="commentContent"
placeholder="写下你的评论..."
rows="3"
className="w-full border rounded p-2 focus:outline-none focus:ring-2 focus:ring-blue-300"
autoComplete="off"
/>
<button
type="submit"
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded transition"
>
发布评论
</button>
</form>
</div>
);
}
// 页面组件:评论功能演示
export default function OptimisticCommentDemo() {
const [errorMessage, setErrorMessage] = useState("");
const [comments, setComments] = useState([
{ id: "initial-1", text: "第一条评论!", isPending: false },
]);
// 处理真实评论提交
async function handleCommentSubmit(formData, pendingId) {
const content = formData.get("commentContent");
// 模拟 API 调用
const response = await apiPostComment({ content });
if (response.error) {
setErrorMessage(`"${content}" 提交失败:${response.error.message}`);
return response;
} else {
setErrorMessage("");
// ✅ 成功后:添加真实评论(不带 isPending)
setComments((prev) => [
...prev,
{
id: `comment-${Date.now()}`,
text: response.data.text,
isPending: false,
},
]);
}
return response;
}
return (
<div className="p-6 max-w-2xl mx-auto">
<h2 className="text-2xl font-bold text-gray-800 mb-4">
乐观更新评论系统演示
</h2>
<CommentSection commentList={comments} onAddComment={handleCommentSubmit} />
{errorMessage && (
<p className="text-red-600 mt-4 text-sm bg-red-50 p-3 rounded">
{errorMessage}
</p>
)}
</div>
);
}
2、use (支持异步)
1、use可读取promise
示例:
js
import { useState, use, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
// 模拟异步获取天气数据
function fetchWeatherData(city) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 50% 概率失败,增加错误边界演示效果
if (Math.random() < 0.5) {
reject(new Error("天气服务暂时不可用"));
} else {
resolve({
city,
temperature: Math.round(Math.random() * 30),
condition: ["晴", "多云", "小雨", "雷阵雨"][Math.floor(Math.random() * 4)],
lastUpdated: new Date().toLocaleTimeString(),
});
}
}, 1200);
});
}
export default function WeatherDashboard() {
const [weatherQuery, setWeatherQuery] = useState(null);
const [isFetching, setFetching] = useState(false);
const handleFetchWeather = () => {
setWeatherQuery(fetchWeatherData("杭州"));
setFetching(true);
};
if (!isFetching) {
return (
<div className="text-center">
<h2 className="text-xl font-semibold mb-4">🌤️ 天气信息看板</h2>
<button
onClick={handleFetchWeather}
className="bg-green-500 hover:bg-green-600 text-white px-5 py-2 rounded transition"
>
获取杭州天气
</button>
</div>
);
}
return <WeatherDisplay weatherPromise={weatherQuery} />;
}
// 展示天气数据的容器(含错误和加载状态)
function WeatherDisplay({ weatherPromise }) {
console.log(
"%c [ WeatherDisplay ]-38",
"font-size:13px; background:#4B5563; color:#fff; padding:2px 6px;",
"渲染 WeatherDisplay"
);
return (
<div className="max-w-md mx-auto p-4 border rounded-lg shadow bg-white">
<h3 className="text-lg font-medium text-gray-800 mb-3">🌤️ 实时天气</h3>
<ErrorBoundary fallback={<WeatherError />}>
<Suspense fallback={<WeatherLoading />}>
<WeatherCard dataPromise={weatherPromise} />
</Suspense>
</ErrorBoundary>
</div>
);
}
// 加载中状态
function WeatherLoading() {
return (
<div className="text-center text-gray-500 animate-pulse">
<p>📡 正在连接天气服务器...</p>
<p className="text-sm mt-1">请稍候</p>
</div>
);
}
// 错误状态
function WeatherError() {
return (
<div className="text-center text-red-600">
<p>❌ 获取天气失败</p>
<p className="text-sm mt-1">请检查网络或稍后重试</p>
</div>
);
}
// 实际渲染天气数据(使用 use)
function WeatherCard({ dataPromise }) {
const weather = use(dataPromise);
return (
<div>
<p><strong>城市:</strong>{weather.city}</p>
<p><strong>温度:</strong>{weather.temperature}°C</p>
<p><strong>天气:</strong>{weather.condition}</p>
<p className="text-xs text-gray-500 mt-2">
更新时间:{weather.lastUpdated}
</p>
</div>
);
}
2、use可读取context。
示例:
js
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>
);
3、Ref 支持在 props 中转发使用
从19开始,ref可以作为prop在函数组件中使用了。之前函数组件中想要使用ref,必须使用forwardRef
。这表示着从React19开始,forwardRef
可能要被弃用了。会对一些三方组件库有比较大的影响,因为基本上每个组件都会用到ref,之前的写法都是使用的forwardRef
。
1、React19给ref加上了自己的清理函数,之前可以用useEffect清理,现在ref有自己的清理函数了。
示例:
js
<div
ref={(ref) => {
// 当元素从DOM中移除时的引用。
return () => {
// ref 清理函数
};
}}
/>
4、Context 改动
之前使用 Context 基本就是三步走:创建Context;Provider传递value;后代组件消费value。React19中简化了Context.Provider,可以直接使用Context代替Provider:
示例:
js
const ThemeContext = createContext('');
function App({children}) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
5、其他改动
1、支持文档元数据
示例:
js
export default function Demo ({ props }) {
return (
<section>
<title>{props.title}</title>
<meta name="name" content="content" />
<Link rel="test" href="xxx" />
<meta name="keywords" content={props.keywords} />
<p>测试内容...</p>
</section>
)
}
2、支持样式表
js
export default function Demo ({ props }) {
return (
<section>
<link rel="stylesheet" href="xxx" />
<p>测试内容...</p>
</section>
)
}
3、支持异步脚本
js
export default function Demo ({ props }) {
return (
<section>
<script async={true} src="xxx" />
<p>测试内容...</p>
</section>
)
}
6、支持自定义元素
在之前的版本中,React会把不认识的props当做attributes来处理,在React19中,此行为改为与自定义元素实例上的属性匹配的 props 被分配为 properties,其他的被分配为attributes。