AICoding快速做了一个React19和React16的特性对比网站
🎉 React 19 正式发布了!这次更新不是换了个壳,而是真的在帮你少写代码。本文通过和 React 16 的对比,用简单的比喻带你搞懂每个新特性,看完保证你立刻想升级。
前言:React 19 升级了什么?
如果把 React 16 比作一辆手动挡汽车 ,那 React 19 就是自动挡------你还是在开同一辆车,但省掉了很多繁琐操作,开起来更顺畅了。
本文覆盖 React 19 的 9 个核心特性,每个都配有新旧代码对比,一看就懂。
一、use() Hook:读数据再也不用"三件套"了
🤔 以前怎么做
以前从接口拿数据,必须写 useState + useEffect + 手动判断 loading/error 的三件套,就像每次去超市买东西,都要先写购物清单、排队领号、再等叫号------步骤缺一不可。
jsx
// React 16:必须手动管理所有状态
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetchUser(userId)
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error!</div>;
return <div>{user.name}</div>;
}
光是"显示一个用户名",就要写将近 20 行。
✅ React 19 怎么做
React 19 的 use() Hook 就像给了你一个**"魔法读取器"**------把 Promise 塞进去,直接拿到结果,加载中的状态交给 Suspense 来管。
jsx
// React 19:直接读取,爽!
import { use, Suspense } from 'react';
function UserProfile({ userPromise }) {
const user = use(userPromise); // 就这一行!
return <div>{user.name}</div>;
}
function App() {
const userPromise = fetchUser(1);
return (
<Suspense fallback={<div>Loading...</div>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}
省了多少? 20 行 → 2 行核心逻辑,loading 和 error 状态完全不用自己管。
💡 一句话记住 :
use()就像奶茶店的自助取餐柜,你下单(传入 Promise),等好了直接取(读到数据),等待过程(Suspense)自动帮你排队。
二、Server Actions:表单提交不用再写 API 了
🤔 以前怎么做
以前写一个表单提交,需要:
- 前端写
handleSubmit函数 - 手写
fetch请求 - 后端单独写一个
/api/submit接口 - 还要自己管
loading状态
就像你给朋友发快递,要先打电话、再填单子、再去前台、再等确认------中间每步都要自己操作。
jsx
// React 16:前后端需要分别维护
function ContactForm() {
const [status, setStatus] = useState('idle');
const handleSubmit = async (e) => {
e.preventDefault();
setStatus('submitting');
const formData = new FormData(e.target);
try {
await fetch('/api/submit', { // 还得单独建 API 路由
method: 'POST',
body: JSON.stringify({ name: formData.get('name') }),
headers: { 'Content-Type': 'application/json' }
});
setStatus('success');
} catch {
setStatus('error');
}
};
return (
<form onSubmit={handleSubmit}>
<input name="name" />
<button disabled={status === 'submitting'}>提交</button>
</form>
);
}
✅ React 19 怎么做
React 19 的 Server Actions 让你直接在组件里定义服务端函数,form 的 action 属性直接传进去就行了。
jsx
// React 19:函数加个标记,直接用
async function submitForm(formData) {
'use server'; // 一行声明,这个函数在服务器跑
const name = formData.get('name');
await db.users.create({ name });
}
function ContactForm() {
return (
<form action={submitForm}> {/* 直接传函数! */}
<input name="name" />
<button type="submit">提交</button>
</form>
);
}
省了多少? 不用写 /api/submit 接口,不用手写 fetch,不用管状态。
💡 一句话记住:Server Actions 就像微信支付------你只管扫码确认(调用函数),钱从哪儿走、怎么到账,后台自动帮你搞定。
三、useFormStatus:按钮终于知道表单在忙了
🤔 以前怎么做
你有一个「提交按钮」组件,它想知道父级表单是否正在提交中,以便显示 loading 状态。在 React 16 里,你必须把状态从父组件一层层传下去(prop drilling),或者创建一个 Context------就像老板开会要知道项目进度,得一个部门一个部门打电话问。
jsx
// React 16:状态传递好麻烦
const FormContext = createContext({ pending: false });
function SubmitButton() {
const { pending } = useContext(FormContext); // 要专门建一个 Context
return (
<button disabled={pending}>
{pending ? '提交中...' : '提交'}
</button>
);
}
function MyForm() {
const [pending, setPending] = useState(false);
// 还得自己管状态...
return (
<FormContext.Provider value={{ pending }}>
<form onSubmit={...}>
<SubmitButton />
</form>
</FormContext.Provider>
);
}
✅ React 19 怎么做
useFormStatus 让子组件天生就能感知父表单状态,不需要任何传递。
jsx
// React 19:自动感知,什么都不用传
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus(); // 自动知道父表单状态!
return (
<button disabled={pending}>
{pending ? '提交中...' : '提交'}
</button>
);
}
function MyForm() {
return (
<form action={serverAction}>
<input name="email" />
<SubmitButton /> {/* 直接用,不传任何 props */}
</form>
);
}
💡 一句话记住 :
useFormStatus就像广播电台------表单"开播"了(提交中),收音机(按钮)自动就知道,不需要有人专门去通知。
四、useOptimistic:点了就立刻显示,不用等服务器
🤔 以前怎么做
做一个点赞功能,点击后要等服务器响应才更新 UI,用户会感觉"卡"。想做成"立刻显示"的效果,就得自己写一套乐观更新逻辑:
- 先临时更新 UI
- 等接口成功后替换真实数据
- 接口失败了还得手动回滚
就像你发了一条朋友圈,要等服务器确认才能看到------而现实中微信是先让你看到,有问题再悄悄处理。
jsx
// React 16:手动管理乐观更新,容易出 bug
function TodoList({ todos: initialTodos, addTodo }) {
const [todos, setTodos] = useState(initialTodos);
const handleAdd = async (text) => {
const tempId = Date.now();
// 先临时显示
setTodos(prev => [...prev, { id: tempId, text, pending: true }]);
try {
const newTodo = await addTodo(text);
// 成功:替换临时项
setTodos(prev => prev.map(t => t.id === tempId ? newTodo : t));
} catch {
// 失败:手动回滚
setTodos(prev => prev.filter(t => t.id !== tempId));
}
};
// ...
}
✅ React 19 怎么做
useOptimistic 把这一套逻辑内置了,你只需要描述"乐观的样子",其他的 React 帮你处理。
jsx
// React 19:两行搞定乐观更新
import { useOptimistic } from 'react';
function TodoList({ todos, addTodo }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, { ...newTodo, pending: true }]
);
const handleAdd = async (text) => {
addOptimisticTodo({ id: Date.now(), text }); // 立即显示
await addTodo(text); // 实际提交(失败会自动回滚)
};
return (
<ul>
{optimisticTodos.map(todo => (
<li key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>
{todo.text}
</li>
))}
</ul>
);
}
💡 一句话记住 :
useOptimistic就像外卖 App 的"预计送达"------你一下单就显示送货中,真正到了再更新状态,失败了自动消失,你完全不用管中间过程。
五、ref 直接当 prop 传:告别 forwardRef
🤔 以前怎么做
你有一个自定义 <Input> 组件,想从外部拿到它的 DOM 节点(比如让它聚焦)。在 React 16 里,直接传 ref 是不行的,必须用 forwardRef 包一层------就像你想给朋友一个礼物,但必须先装进一个特定的礼品盒才能交出去。
jsx
// React 16:必须加 forwardRef 包装
import { forwardRef } from 'react';
const Input = forwardRef(function Input(props, ref) {
return <input ref={ref} {...props} />;
});
// 使用者看不出来,但写组件的人知道有多麻烦
✅ React 19 怎么做
React 19 里,ref 就是个普通 prop,直接收、直接用。
jsx
// React 19:ref 就是普通 prop
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
// 调用方直接传,简洁!
function App() {
const inputRef = useRef(null);
return (
<div>
<Input ref={inputRef} placeholder="点我聚焦" />
<button onClick={() => inputRef.current.focus()}>聚焦</button>
</div>
);
}
💡 一句话记住 :以前
ref是"特殊乘客",必须走专用通道(forwardRef)。React 19 让它跟普通乘客一样,随便哪个门进都行。
六、Document Metadata:管 SEO 再也不用装插件了
🤔 以前怎么做
以前想在组件里动态修改页面标题、meta 标签,必须装 react-helmet 或者用框架提供的 <Head> 组件------就像想挂一幅画,必须先找物业批准、填申请表才行。
jsx
// React 16:需要第三方库
import { Helmet } from 'react-helmet';
function BlogPost({ post }) {
return (
<article>
<Helmet>
<title>{post.title} | My Blog</title>
<meta name="description" content={post.excerpt} />
</Helmet>
<h1>{post.title}</h1>
</article>
);
}
✅ React 19 怎么做
直接在组件里写 <title>、<meta>,React 会自动把它们放到 <head> 里。
jsx
// React 19:原生支持,直接写!
function BlogPost({ post }) {
return (
<article>
<title>{post.title} | My Blog</title> {/* 直接写! */}
<meta name="description" content={post.excerpt} />
<h1>{post.title}</h1>
</article>
);
}
💡 一句话记住:以前写 meta 标签像去银行开户需要各种证明,现在 React 19 给你开了绿色通道,直接走,不用审批。
七、Asset 预加载 API:用 JS 控制资源加载
🤔 以前怎么做
想预加载字体、脚本,要么在 HTML 里手动加 <link rel="preload">,要么通过 react-helmet 动态插入------资源管理分散在 HTML 和 JS 两处,难以维护。
jsx
// React 16:在 HTML 或 Helmet 里手动加
<Helmet>
<link rel="dns-prefetch" href="https://cdn.example.com" />
<link rel="preload" href="/fonts/inter.woff2" as="font" />
</Helmet>
✅ React 19 怎么做
React 19 提供了 preload、prefetchDNS、preinit 等 API,直接在 JS 里调用:
jsx
// React 19:在代码里统一管理
import { prefetchDNS, preload, preinit } from 'react-dom';
function App() {
prefetchDNS('https://cdn.example.com');
preload('/fonts/inter.woff2', { as: 'font' });
preinit('/scripts/analytics.js', { as: 'script' });
return <main>...</main>;
}
💡 一句话记住:以前资源预加载写在 HTML 里,就像把工作任务写在便利贴上贴墙上------不好管。现在全部写进代码,统一放在"任务清单"里。
八、增强错误处理:报错信息更清晰,错误边界更好用
🤔 以前怎么做
React 16 里,错误边界只能用类组件实现,写起来又臭又长,而且不同来源的错误(被捕获 vs 未被捕获)很难区分。
jsx
// React 16:必须写类组件
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
logErrorToService(error, errorInfo.componentStack);
}
render() {
if (this.state.hasError) {
return <h1>出错了</h1>;
}
return this.props.children;
}
}
就算在 2024 年,你还是得为了错误边界专门写一个类组件。
✅ React 19 怎么做
React 19 在 createRoot 上提供了三种错误回调,可以精细区分错误类型:
jsx
// React 19:在根节点统一配置错误处理
const root = createRoot(document.getElementById('root'), {
onCaughtError(error, errorInfo) {
// 被错误边界捕获的错误
logError(error, errorInfo.componentStack);
},
onUncaughtError(error, errorInfo) {
// 未被捕获的错误
showErrorDialog(error);
},
onRecoverableError(error) {
// 可自动恢复的错误(如 hydration 不一致)
console.warn(error);
}
});
💡 一句话记住:React 16 的错误处理像保安------只有一个岗亭,什么人都拦在一起。React 19 装了分类闸机:普通人走这边、VIP 走那边、问题人员走那边,各司其职。
九、Context 简化:.Provider 消失了
🤔 以前怎么做
用 Context 传数据,必须写 <ThemeContext.Provider> 这种带 .Provider 的写法,感觉像买了个手机还必须套个指定品牌的壳才能用。
jsx
// React 16:必须写 .Provider
<ThemeContext.Provider value="dark">
<App />
</ThemeContext.Provider>
✅ React 19 怎么做
直接用 Context 本身作为组件:
jsx
// React 19:直接用,省掉 .Provider
<ThemeContext value="dark">
<App />
</ThemeContext>
而且读取时还可以用更灵活的 use() 替代 useContext(),最大的区别是 use() 可以在 if 语句里调用 (useContext 不行):
jsx
// React 19:use() 可以在条件中使用
function Page({ isLoggedIn }) {
if (!isLoggedIn) return <Login />;
const theme = use(ThemeContext); // ✅ 在 if 之后调用没问题
return <div className={theme}>...</div>;
}
💡 一句话记住:以前用 Context 像开车必须带驾照(.Provider),React 19 直接把驾照内置到车里了------上去就能开。
总结对比表
| 特性 | React 16 | React 19 | 节省了什么 |
|---|---|---|---|
| 异步数据获取 | useState + useEffect 三件套 |
use() + Suspense |
减少 ~70% 样板代码 |
| 表单提交 | 手写 fetch + API 路由 |
Server Actions | 消除客户端/服务端分离 |
| 表单状态共享 | Context 或 prop drilling | useFormStatus |
零传参自动感知 |
| 乐观更新 | 手写临时状态 + 回滚逻辑 | useOptimistic |
自动管理,消除 bug |
| ref 转发 | 必须 forwardRef 包装 |
直接当 prop 传 | 告别包装地狱 |
| SEO 元数据 | react-helmet 等第三方库 |
原生 <title>/<meta> |
零依赖 |
| 资源预加载 | HTML 手写 / Helmet | preload/preinit API |
JS 统一管理 |
| 错误处理 | 类组件错误边界 | createRoot 回调 + 细分类型 |
告别类组件 |
| Context 使用 | <Context.Provider> |
<Context> 直接用 |
语法更简洁 |
该不该升级?
✅ 推荐升级的场景:
- 新项目:直接用 React 19,享受所有新特性
- 用了 Next.js 14+:Server Actions 已经是主推方案
- 有大量表单交互的项目:
useFormStatus+useOptimistic省代码很明显
⚠️ 谨慎升级的场景:
- 大型存量项目:Server Actions 依赖特定框架(Next.js/Remix),不是纯前端能用的
- 严格依赖类组件的老项目:需要逐步迁移
- 团队还没准备好:
use()的心智模型和以前不一样
结语
React 19 最大的变化不是增加了多少功能,而是帮你删掉了多少样板代码。每个新 API 背后都是 React 团队在说:"这个事儿你不用管了,交给我们。"
从 16 到 19,变化的不只是版本号,而是写 React 的姿势。
如果这篇文章对你有帮助,欢迎点赞收藏 👍 有问题欢迎评论区交流~