大家好~ React 19 里有不少令人兴奋的新特性,其中 use() 和 useOptimistic() 两个新 Hooks 堪称"业务效率提升利器"。前者彻底改变了异步数据在组件中的使用方式,后者让乐观更新的实现成本直降为零。
但很多开发者在初次接触这两个 Hooks 时,难免会有困惑:use() 和 useEffect、Suspense 是什么关系?useOptimistic 到底解决了什么痛点?实际业务中该怎么落地?今天这篇文章,我们就从"痛点分析"出发,一步步拆解这两个 Hooks 的核心原理,再通过 5 个真实业务案例,带你彻底掌握它们的使用方法和落地技巧,让新特性真正服务于业务~
一、先搞懂:React 19 为什么要新增这两个 Hooks?
在聊具体用法之前,我们先回到业务场景,看看过去的 React 版本中,我们面临哪些棘手的问题------这正是新 Hooks 诞生的原因。
1. 痛点 1:异步数据获取的"模板代码冗余"
在 React 18 及之前,我们获取异步数据(比如接口请求),通常需要用 useState 管理加载状态、数据状态、错误状态,再用 useEffect 触发请求,代码模板化严重:
javascript
// React 18 及之前的异步数据获取
function UserProfile({ userId }) {
// 3 个状态管理:加载中、数据、错误
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// 重置状态
setLoading(true);
setError(null);
// 接口请求
const fetchUser = async () => {
try {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('请求失败');
const data = await res.json();
setUser(data); // 成功设置数据
} catch (err) {
setError(err.message); // 失败设置错误
} finally {
setLoading(false); // 无论成败,结束加载
}
};
fetchUser();
}, [userId]);
// 加载中、错误、成功的条件渲染
if (loading) return <div>加载中...</div>;
if (error) return <div>错误:{error}</div>;
if (!user) return <div>无数据</div>;
return (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
这种写法的问题很明显:每个异步请求都要重复写"状态管理+错误处理+条件渲染"的模板代码,代码冗余,且容易遗漏边缘情况(比如组件卸载前请求还没完成导致的内存泄漏)。
2. 痛点 2:乐观更新的"实现复杂且易出错"
乐观更新是提升用户体验的关键技巧------比如用户点击"点赞"按钮时,先立即更新页面状态,再发送请求,等请求成功后确认状态,失败后回滚。但在 React 18 及之前,实现乐观更新需要手动管理"临时状态"和"真实状态",逻辑繁琐且容易出错:
javascript
// React 18 及之前的乐观更新实现(点赞功能)
function LikeButton({ postId, initialLikes }) {
const [likes, setLikes] = useState(initialLikes);
const [isPending, setIsPending] = useState(false); // 标记请求中
const handleLike = async () => {
// 1. 乐观更新:先修改本地状态
const prevLikes = likes;
setLikes(prev => prev + 1);
setIsPending(true);
try {
// 2. 发送请求
const res = await fetch(`/api/posts/${postId}/like`, { method: 'POST' });
if (!res.ok) throw new Error('点赞失败');
} catch (err) {
// 3. 请求失败:回滚状态
setLikes(prevLikes);
alert(err.message);
} finally {
// 4. 结束请求状态
setIsPending(false);
}
};
return (
<button onClick={handleLike} disabled={isPending}>
点赞 ({likes})
</button>
);
}
这种写法需要手动记录"之前的状态"用于回滚,还要处理"请求中状态"防止重复点击,逻辑分散在多个地方,维护成本高。
3. 新 Hooks 的核心价值
React 19 新增的 use() 和 useOptimistic(),正是为了解决这两个痛点:
use():简化异步数据获取,自动处理加载、错误状态,配合 Suspense 实现"等待- fallback"逻辑,消除模板代码;useOptimistic():内置乐观更新逻辑,只需定义"当前状态"和"乐观更新函数",自动处理"临时状态展示"和"失败回滚",无需手动管理。
二、use() Hook 详解:异步数据的"极简使用方式"
use() 是 React 19 推出的"异步数据消费 Hook",核心作用是:直接"消费"Promise 对象,自动等待 Promise 决议(成功/失败),并将结果作为同步值返回。配合 Suspense 组件,就能轻松实现"加载中 fallback",彻底告别手动管理 loading/error 状态。
1. 基础用法:替代 useEffect + useState 获取异步数据
我们用 use() 重写上面的"用户信息获取"案例,看看代码能精简多少:
javascript
// React 19 use() 实现异步数据获取
import { use } from 'react';
// 1. 定义异步请求函数(返回 Promise)
async function fetchUser(userId) {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('请求用户信息失败');
return res.json();
}
// 2. 组件中使用 use() 消费 Promise
function UserProfile({ userId }) {
// use() 直接接收 Promise,等待决议后返回数据
const user = use(fetchUser(userId));
// 无需手动处理 loading/error,由 Suspense 接管
return (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
// 3. 父组件用 Suspense 包裹,设置加载中 fallback
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<UserProfile userId="123" />
</Suspense>
);
}
对比之前的代码,变化非常明显:
- 去掉了
useState管理的 loading、error、user 状态; - 去掉了
useEffect触发请求的逻辑; - 加载中的状态由
Suspense组件统一处理,只需设置fallback; - 错误处理:可以用
ErrorBoundary组件统一捕获(下文会讲)。
2. 核心原理:use() 是如何工作的?
很多人会误以为 use() 是"将异步变成同步",但本质上,它是 配合 React 的 Suspense 机制,实现了"异步数据的同步式写法" 。其核心原理可以拆解为 3 步:
- 接收 Promise 并订阅:use() 接收一个 Promise 对象后,会立即订阅这个 Promise 的决议状态(成功/失败);
- 触发 Suspense 挂起:在 Promise 还未决议时,use() 会抛出一个"特殊的 Promise",这个 Promise 会被 React 捕获,从而触发 Suspense 组件的 fallback 渲染;
- Promise 决议后重新渲染:当 Promise 成功决议(resolve),use() 会返回决议后的值,组件重新渲染并展示数据;如果 Promise 失败(reject),会抛出错误,需要用 ErrorBoundary 捕获。
3. 进阶用法:错误处理与依赖更新
use() 本身不处理错误,需要配合 ErrorBoundary 组件捕获异步请求中的错误。同时,当 use() 依赖的参数变化时(如 userId 改变),会自动重新执行异步函数,触发新的请求。
案例 1:用 ErrorBoundary 处理错误
javascript
// 1. 实现 ErrorBoundary 组件(捕获子组件错误)
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error }; // 捕获错误,更新状态
}
componentDidCatch(error, errorInfo) {
console.error('请求错误:', error, errorInfo); // 日志上报
}
render() {
if (this.state.hasError) {
// 错误状态下的 UI
return <div>加载失败:{this.state.error.message}</div>;
}
return this.props.children;
}
}
// 2. 父组件组合 Suspense 和 ErrorBoundary
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>加载中...</div>}>
<UserProfile userId="123" />
</Suspense>
</ErrorBoundary>
);
}
案例 2:依赖参数变化自动更新
当 userId 变化时,use() 会自动重新调用 fetchUser,触发新的请求,无需手动用 useEffect 监听依赖:
ini
function UserList() {
const [selectedUserId, setSelectedUserId] = useState('123');
return (
<div>
<div>
<button onClick={() => setSelectedUserId('123')}>用户 123</button>
<button onClick={() => setSelectedUserId('456')}>用户 456</button>
</div>
<Suspense fallback={<div>加载中...</div>}>
<UserProfile userId={selectedUserId} />
</Suspense>
</div>
);
}
4. 注意事项:use() 的使用限制
use() 虽然强大,但有几个关键限制,必须遵守,否则会导致组件行为异常:
- 只能在组件顶层或自定义 Hook 顶层调用:不能在条件语句(if)、循环(for)、事件处理函数中调用(和其他 Hooks 一样,遵循 Hooks 规则);
- 不能用于非 Suspense 环境:如果组件没有被 Suspense 包裹,use() 抛出的 Promise 会导致组件崩溃;
- 不支持取消请求(需手动实现) :use() 本身不提供取消请求的能力,需要配合 AbortController 实现;
- 不要用于同步值:use() 设计用于消费 Promise,传入同步值会导致不必要的性能开销。
补充:用 AbortController 取消请求
javascript
async function fetchUser(userId, signal) {
const res = await fetch(`/api/users/${userId}`, { signal }); // 传入信号
if (!res.ok) throw new Error('请求用户信息失败');
return res.json();
}
function UserProfile({ userId }) {
// 创建 AbortController,组件卸载时取消请求
const controller = useRef(new AbortController());
useEffect(() => {
return () => controller.current.abort(); // 组件卸载,取消请求
}, []);
// 传入 signal 给 fetchUser
const user = use(fetchUser(userId, controller.current.signal));
return (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
三、useOptimistic() Hook 详解:乐观更新的"零成本实现"
useOptimistic() 是 React 19 专门为"乐观更新"场景设计的 Hook,核心作用是:在发送异步请求的同时,立即展示"预期的更新结果",等请求成功后确认状态,失败后自动回滚到之前的状态。无需手动管理临时状态和回滚逻辑,极大简化了乐观更新的实现。
1. 基础用法:重构"点赞功能"
我们用 useOptimistic() 重写之前的"点赞功能"案例,感受一下简化效果:
javascript
import { useOptimistic } from 'react';
// 1. 异步请求函数(点赞接口)
async function likePost(postId) {
const res = await fetch(`/api/posts/${postId}/like`, { method: 'POST' });
if (!res.ok) throw new Error('点赞失败');
return res.json(); // 成功返回最新的点赞数
}
function LikeButton({ postId, initialLikes }) {
// 2. 使用 useOptimistic:传入初始状态和乐观更新函数
const [optimisticLikes, addLike] = useOptimistic(
initialLikes, // 初始状态(真实的点赞数)
(currentLikes) => currentLikes + 1 // 乐观更新函数:接收当前状态,返回预期状态
);
// 3. 点击事件:调用 addLike 触发乐观更新,同时发送请求
const handleLike = async () => {
// 触发乐观更新:立即更新 optimisticLikes 为 currentLikes + 1
addLike();
try {
// 发送请求,等待真实结果
const data = await likePost(postId);
// 请求成功:无需额外操作,useOptimistic 会自动用真实结果同步状态
} catch (err) {
// 请求失败:无需手动回滚,useOptimistic 会自动恢复到之前的状态
alert(err.message);
}
};
return (
<button onClick={handleLike}>
点赞 ({optimisticLikes}) // 展示乐观更新后的状态
</button>
);
}
对比之前的实现,核心简化点:
- 去掉了
useState管理的likes(真实状态)和isPending(请求中状态); - 无需手动记录"之前的状态"用于回滚,useOptimistic 会自动管理;
- 只需调用
addLike()就能触发乐观更新,逻辑更集中。
2. 核心原理:useOptimistic() 是如何实现"自动回滚"的?
useOptimistic() 的核心逻辑是"维护两个状态":真实状态(realState) 和 乐观状态(optimisticState) 。其工作流程可以拆解为 4 步:
- 初始化 :useOptimistic 接收
initialState作为真实状态的初始值,乐观状态初始值与真实状态一致; - 触发乐观更新:调用 useOptimistic 返回的更新函数(如 addLike)时,会根据传入的"乐观更新函数"计算出预期的乐观状态,并立即更新乐观状态,组件重新渲染,展示乐观结果;
- 等待异步请求:异步请求发送后,useOptimistic 会等待请求结果;
- 同步真实状态: - 若请求成功:用真实结果更新"真实状态",同时将"乐观状态"同步为真实状态(确保两者一致); - 若请求失败:放弃乐观状态,将"乐观状态"回滚到"真实状态",组件重新渲染,恢复之前的结果。
useOptimistic() 工作流程流程图
3. 进阶用法:复杂状态的乐观更新
useOptimistic() 不仅支持数字这种简单状态,也支持对象、数组等复杂状态。只需在"乐观更新函数"中正确处理复杂状态的变更即可。
案例:评论列表的乐观更新(复杂对象数组)
javascript
import { useOptimistic, useState } from 'react';
// 异步请求:提交评论
async function submitComment(postId, comment) {
const res = await fetch(`/api/posts/${postId}/comments`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(comment),
});
if (!res.ok) throw new Error('评论提交失败');
return res.json(); // 成功返回包含 id 的完整评论
}
function CommentSection({ postId }) {
const [content, setContent] = useState('');
// 初始评论列表(空数组)
const [comments, addComment] = useOptimistic(
[],
// 乐观更新函数:接收当前评论列表,返回预期的新列表
(currentComments, newComment) => [
...currentComments,
{ ...newComment, id: 'temp-id' }, // 临时 id,请求成功后会替换
]
);
const handleSubmit = async (e) => {
e.preventDefault();
if (!content.trim()) return;
// 构建评论数据
const newComment = { content, author: '我', time: new Date().toLocaleString() };
// 触发乐观更新:传入新评论,更新评论列表
addComment(newComment);
// 清空输入框
setContent('');
try {
// 发送请求,获取真实评论(包含真实 id)
const realComment = await submitComment(postId, newComment);
// 请求成功:useOptimistic 会自动用真实评论替换临时评论?不,需要手动同步!
// 注意:复杂状态下,请求成功后需要手动更新真实状态
// 这里的关键:addComment 只是触发乐观更新,真实状态需要我们手动维护?
// 纠正:useOptimistic 的第二个参数是"乐观更新函数",接收的参数是(currentState, ...args)
// 当调用 addComment(...args) 时,乐观状态 = 乐观更新函数(currentState, ...args)
// 而真实状态的更新,需要我们在请求成功后,通过重新赋值 initialState 实现?
// 正确的复杂状态用法:需要用 useState 管理真实状态,useOptimistic 基于真实状态做乐观更新
// 重新梳理正确写法:
} catch (err) {
alert(err.message);
// 失败无需手动回滚,useOptimistic 自动恢复
}
};
// 正确的复杂状态写法:用 useState 管理真实状态,useOptimistic 包装
const [realComments, setRealComments] = useState([]);
const [optimisticComments, addOptimisticComment] = useOptimistic(
realComments, // 基于真实状态
(current, newComment) => [...current, { ...newComment, id: 'temp' }]
);
const handleSubmitCorrect = async (e) => {
e.preventDefault();
if (!content.trim()) return;
const newComment = { content, author: '我', time: new Date().toLocaleString() };
addOptimisticComment(newComment); // 乐观更新
setContent('');
try {
const realComment = await submitComment(postId, newComment);
setRealComments(prev => [...prev, realComment]); // 更新真实状态
} catch (err) {
alert(err.message);
}
};
return (
<div>
<form onSubmit={handleSubmitCorrect}>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="输入评论..."
/>
<button type="submit">提交评论</button>
</form>
<div className="comments">
{optimisticComments.map(comment => (
<div key={comment.id} className="comment">
<p>{comment.author} {comment.time}</p>
<p>{comment.content}</p>
</div>
))}
</div>
</div>
);
}
复杂状态下的核心要点:
- 用
useState管理"真实状态"(如 realComments); - useOptimistic 的第一个参数传入"真实状态",确保乐观更新基于最新的真实数据;
- 请求成功后,通过
setRealComments更新真实状态,useOptimistic 会自动同步乐观状态与真实状态; - 乐观更新函数中,可以用"临时 id"占位,请求成功后用真实 id 替换。
四、业务落地:5 个高频场景案例
了解了两个 Hooks 的基础用法和原理后,我们结合真实业务场景,看看它们在实际开发中如何落地。
场景 1:用户信息详情页(use() + Suspense + ErrorBoundary)
需求:进入页面加载用户信息,展示加载中状态,加载失败提示错误,支持切换用户。
javascript
import { use, useState } from 'react';
import { Suspense } from 'react';
// 异步请求:获取用户信息
async function fetchUser(userId, signal) {
const res = await fetch(`/api/users/${userId}`, { signal });
if (!res.ok) throw new Error('获取用户信息失败');
return res.json();
}
// 子组件:用户详情
function UserDetail({ userId }) {
const controller = useRef(new AbortController());
useEffect(() => {
return () => controller.current.abort();
}, []);
const user = use(fetchUser(userId, controller.current.signal));
return (
<div className="user-detail">
<img src={user.avatar} alt={user.name} className="avatar" />
<h2>{user.name}</h2>
<p>邮箱:{user.email}</p>
<p>手机:{user.phone}</p>
<p>地址:{user.address}</p>
</div>
);
}
// ErrorBoundary 组件(复用之前的实现)
class ErrorBoundary extends React.Component { /* ... */ }
// 父组件:用户列表 + 详情切换
function UserPage() {
const [selectedUserId, setSelectedUserId] = useState('1');
const userIds = ['1', '2', '3']; // 模拟用户列表
return (
<div className="user-page">
<div className="user-list">
{userIds.map(id => (
<button
key={id}
onClick={() => setSelectedUserId(id)}
className={selectedUserId === id ? 'active' : ''}
>
用户 {id}
</button>
))}
</div>
<ErrorBoundary>
<Suspense fallback={<div className="loading">加载用户信息中...</div>}>
<UserDetail userId={selectedUserId} />
</Suspense>
</ErrorBoundary>
</div>
);
}
场景 2:商品收藏功能(useOptimistic + 接口请求)
需求:点击收藏按钮,立即显示"已收藏"状态,发送收藏请求,失败后回滚状态并提示。
javascript
import { useOptimistic } from 'react';
// 异步请求:收藏/取消收藏商品
async function toggleCollect(goodsId, isCollected) {
const res = await fetch(`/api/goods/${goodsId}/collect`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ isCollected: !isCollected }),
});
if (!res.ok) throw new Error(isCollected ? '取消收藏失败' : '收藏失败');
return res.json();
}
function GoodsCollectButton({ goodsId, initialIsCollected }) {
// 乐观更新:切换收藏状态
const [isCollected, toggleOptimisticCollect] = useOptimistic(
initialIsCollected,
(current) => !current // 乐观更新函数:切换状态
);
const handleToggle = async () => {
// 触发乐观更新
toggleOptimisticCollect();
try {
// 发送请求
await toggleCollect(goodsId, initialIsCollected);
// 成功无需额外操作,若需要更新父组件状态,可通过回调函数
} catch (err) {
alert(err.message);
// 失败自动回滚
}
};
return (
<button
onClick={handleToggle}
className={`collect-btn ${isCollected ? 'collected' : ''}`}
>
{isCollected ? '已收藏' : '收藏'}
{isCollected ? '❤️' : '♡'}
</button>
);
}
场景 3:购物车数量修改(useOptimistic + 节流)
需求:点击"+""-"按钮修改购物车商品数量,立即更新数量,发送请求,失败回滚,防止重复点击。
javascript
import { useOptimistic, useCallback } from 'react';
import { throttle } from 'lodash';
// 异步请求:修改购物车数量
async function updateCartItemCount(cartItemId, count) {
const res = await fetch(`/api/cart/${cartItemId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ count }),
});
if (!res.ok) throw new Error('修改数量失败');
return res.json();
}
function CartItem({ item }) {
const { id, name, price, count } = item;
// 节流:防止频繁点击触发过多请求
const throttledUpdate = useCallback(
throttle(async (newCount) => {
try {
await updateCartItemCount(id, newCount);
} catch (err) {
alert(err.message);
}
}, 300),
[id]
);
// 乐观更新:修改数量
const [optimisticCount, updateOptimisticCount] = useOptimistic(
count,
(current, delta) => {
const newCount = current + delta;
return newCount <= 0 ? 1 : newCount; // 数量不小于 1
}
);
const handleIncrement = () => {
updateOptimisticCount(1); // 增加 1
throttledUpdate(optimisticCount + 1);
};
const handleDecrement = () => {
updateOptimisticCount(-1); // 减少 1
throttledUpdate(optimisticCount - 1);
};
return (
<div className="cart-item">
<h4>{name}</h4>
<p className="price">¥{price.toFixed(2)}</p>
<div className="count-control">
<button onClick={handleDecrement}>-</button>
<span>{optimisticCount}</span>
<button onClick={handleIncrement}>+</button>
</div>
</div>
);
}
场景 4:表单提交乐观更新(useOptimistic + 复杂表单)
需求:提交表单后,立即显示"提交成功"的提示和表单内容,发送请求,失败后回滚提示并显示错误。
javascript
import { useOptimistic, useState } from 'react';
// 异步请求:提交表单
async function submitForm(formData) {
const res = await fetch('/api/form/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData),
});
if (!res.ok) throw new Error('表单提交失败');
return res.json();
}
function FormComponent() {
const [name, setName] = useState('');
const [age, setAge] = useState('');
// 表单提交状态:{ status: 'idle' | 'submitted' | 'error', data: {} }
const initialSubmitState = { status: 'idle', data: {} };
// 乐观更新:提交后立即显示成功状态
const [submitState, submitOptimisticForm] = useOptimistic(
initialSubmitState,
(current, formData) => ({
status: 'submitted',
data: formData,
})
);
const handleSubmit = async (e) => {
e.preventDefault();
const formData = { name, age };
// 触发乐观更新:显示提交成功状态
submitOptimisticForm(formData);
try {
// 发送请求
await submitForm(formData);
// 成功无需额外操作,若需要重置表单,可在这里处理
setName('');
setAge('');
} catch (err) {
// 失败自动回滚到 idle 状态
alert(err.message);
}
};
return (
<div className="form-container">
<form onSubmit={handleSubmit}>
<div className="form-item">
<label>姓名:</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<div className="form-item">
<label>年龄:</label>
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
required
/>
</div>
<button type="submit" disabled={submitState.status === 'submitted'}>
提交
</button>
</form>
// 乐观更新后的提示
{submitState.status === 'submitted' && (
<div className="submit-success">
<h3>提交成功!</h3>
<p>姓名:{submitState.data.name}</p>
<p>年龄:{submitState.data.age}</p>
</div>
)}
</div>
);
}
场景 5:use() 配合自定义 Hook 封装异步请求
需求:封装一个通用的异步请求 Hook,统一处理请求、取消、错误捕获,供多个组件复用。
javascript
import { use, useRef, useEffect } from 'react';
// 自定义 Hook:useAsyncRequest,封装 use() 的异步请求逻辑
function useAsyncRequest(requestFn) {
// 创建 AbortController,用于取消请求
const controller = useRef(new AbortController());
// 组件卸载时取消请求
useEffect(() => {
return () => controller.current.abort();
}, []);
// 执行请求并返回结果
const execute = (...args) => {
return requestFn(...args, controller.current.signal);
};
// 返回 execute 函数和 use() 消费的结果(如果需要立即执行)
return { execute, useResult: (...args) => use(execute(...args)) };
}
// 使用自定义 Hook 封装用户请求
async function fetchUser(userId, signal) {
const res = await fetch(`/api/users/${userId}`, { signal });
if (!res.ok) throw new Error('获取用户信息失败');
return res.json();
}
// 组件中使用自定义 Hook
function UserProfile({ userId }) {
const { useResult } = useAsyncRequest(fetchUser);
const user = useResult(userId); // 直接使用结果
return (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
// 另一个组件:延迟执行请求(比如点击按钮后)
function UserSearch() {
const [userId, setUserId] = useState('');
const { execute } = useAsyncRequest(fetchUser);
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const handleSearch = async () => {
if (!userId) return;
setLoading(true);
try {
const data = await execute(userId);
setUser(data);
} catch (err) {
alert(err.message);
} finally {
setLoading(false);
}
};
return (
<div>
<input
type="text"
value={userId}
onChange={(e) => setUserId(e.target.value)}
placeholder="输入用户 ID"
/>
<button onClick={handleSearch} disabled={loading}>
搜索
</button>
{loading && <div>搜索中...</div>}
{user && (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
)}
</div>
);
}
五、避坑指南:使用 use()/useOptimistic 的 6 个关键注意点
在实际使用过程中,有几个容易踩坑的点,需要特别注意:
1. use() 必须配合 Suspense 使用
如果组件没有被 Suspense 包裹,use() 抛出的 Promise 会导致组件崩溃。即使你不需要加载中状态,也必须用 Suspense 包裹(可以设置 fallback 为 null)。
2. use() 不支持在事件处理函数中调用
use() 必须在组件顶层调用,不能在 onClick、onSubmit 等事件处理函数中使用。如果需要"点击后再请求数据",可以用 useRef 存储 AbortController,在事件处理函数中手动执行异步函数,再用 useState 管理结果。
3. useOptimistic() 的乐观状态是"临时的"
useOptimistic() 的乐观状态只在当前组件渲染周期内有效,组件卸载后会丢失。如果需要持久化状态,还是需要用 useState 或 Redux 等状态管理库管理真实状态。
4. useOptimistic() 不处理请求并发问题
如果短时间内触发多次乐观更新(比如快速点击点赞按钮),会导致多个异步请求并发,可能出现状态混乱。需要配合节流、防抖或请求队列管理,避免并发问题。
5. 错误处理必须用 ErrorBoundary
use() 和 useOptimistic() 都不自带错误处理能力,异步请求的错误必须用 ErrorBoundary 组件捕获,否则会导致整个应用崩溃。
6. useOptimistic() 的乐观更新函数必须是纯函数
乐观更新函数(第二个参数)不能有副作用(比如修改全局变量、发送请求),必须是纯函数,只依赖当前状态和传入的参数,否则会导致状态不一致。
六、核心总结
今天我们详细拆解了 React 19 新 Hooks use() 和 useOptimistic() 的核心原理和业务落地方法,核心要点总结如下:
- use() 的核心价值:简化异步数据获取,自动处理加载状态,配合 Suspense 和 ErrorBoundary 消除模板代码;
- use() 的原理:订阅 Promise,未决议时触发 Suspense 挂起,决议后返回结果或抛出错误;
- useOptimistic() 的核心价值:零成本实现乐观更新,自动管理乐观状态和真实状态,失败后自动回滚;
- useOptimistic() 的原理:维护真实状态和乐观状态,触发更新时先更新乐观状态,请求完成后同步真实状态;
- 落地关键:use() 必须配合 Suspense 和 ErrorBoundary,useOptimistic() 复杂状态需结合 useState 管理真实状态,避免并发和副作用问题。
七、进阶学习方向
掌握了这两个 Hooks 的基础用法后,可以进一步学习以下内容,深化理解:
- use() 与 Suspense Data Fetching 的关系:React 19 对数据获取的整体优化思路;
- useOptimistic() 与并发渲染的协同:在并发模式下如何保证乐观更新的稳定性;
- React 19 其他新特性:如 Action、Server Components 等,如何与 use()/useOptimistic() 配合使用;
- 源码阅读:查看 React 源码中 use() 和 useOptimistic() 的实现,深入理解底层逻辑。
如果这篇文章对你有帮助,欢迎点赞、收藏、转发~ 有任何问题也可以在评论区留言交流~ 我们下期再见!