useOptimistic源码实现
useOptimistic 是 React 19 提供的 Hook,在异步操作(如 Server Action、网络请求)完成前,先展示预期的 UI 状态,操作失败或完成后自动回退到真实数据。
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
| 参数 / 返回值 | 说明 |
|---|---|
state | 真实状态,通常来自 props 或 useState |
updateFn(currentState, optimisticValue) | 可选,根据当前状态和传入值计算乐观状态 |
optimisticState | 过渡期间可能覆盖 state 的展示值 |
addOptimistic(value) | 触发一次乐观更新,包裹在 startTransition 中 |
基础示例:点赞
用户点击后立即切换 UI,请求失败时再回滚。
import { useOptimistic, useTransition } from 'react';
function LikeButton({ likes, onLike }) {
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(current, delta) => current + delta
);
const [, startTransition] = useTransition();
const handleClick = () => {
startTransition(async () => {
addOptimisticLike(1); // 先 +1
try {
await onLike(); // 请求成功后,父组件传入新的 likes,自动覆盖
} catch {
// 失败时父组件 state 未变,isPending 结束后 UI 自动回滚
}
});
};
return (
<button onClick={handleClick}>
❤️ {optimisticLikes}
</button>
);
}
列表示例:发送消息
列表数据来自服务端,发送时先把「发送中」的消息插入列表。
function MessageList({ messages, sendMessage }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(current, newMessage) => [...current, { ...newMessage, sending: true }]
);
async function handleSubmit(formData) {
const text = formData.get('text');
const temp = { id: crypto.randomUUID(), text, sending: true };
addOptimisticMessage(temp);
try {
await sendMessage(text);
// 成功后父组件刷新 messages,optimistic 状态自动让位
} catch (e) {
// 失败回滚到 messages
}
}
return (
<>
<ul>
{optimisticMessages.map((msg) => (
<li key={msg.id} style={{ opacity: msg.sending ? 0.6 : 1 }}>
{msg.text}
</li>
))}
</ul>
<form action={handleSubmit}>
<input name="text" />
<button type="submit">发送</button>
</form>
</>
);
}
配合 Server Action
React 19 中常见写法:Action 执行期间展示乐观 UI,Action 返回后 state 更新,乐观值自动失效。
'use client';
import { useOptimistic } from 'react';
import { toggleTodo } from './actions';
export function TodoList({ todos }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(current, { id, done }) =>
current.map((todo) =>
todo.id === id ? { ...todo, done } : todo
)
);
return (
<ul>
{optimisticTodos.map((todo) => (
<li key={todo.id}>
<form
action={async () => {
addOptimisticTodo({ id: todo.id, done: !todo.done });
await toggleTodo(todo.id);
}}
>
<button type="submit">
{todo.done ? '✅' : '⬜'} {todo.title}
</button>
</form>
</li>
))}
</ul>
);
}
要点
- 乐观状态是临时的:
isPending === false后,optimisticState始终等于真实state。 addOptimistic必须在 Transition 中:内部通过startTransition调度,避免阻塞输入等高优先级更新。- 失败回滚无需手动处理:只要真实
state没变,过渡结束后 UI 自然恢复。 - 与
useActionState互补:useActionState管 Action 的 pending / 返回值;useOptimistic管过渡期间的即时 UI。
简化源码实现
function useOptimistic(state, updateFn) {
// 内部状态,用于在过渡期间覆盖用户传入的 state
const [optimisticState, setOptimisticState] = useState(null);
// 记录当前是否处于过渡期的标志
const [isPending, startTransition] = useTransition();
// 实际返回给用户的“乐观状态”
// 如果在过渡期内且有乐观更新,则使用乐观值;否则使用原始 state
const currentOptimisticState = isPending && optimisticState !== null
? optimisticState
: state;
const addOptimistic = (value) => {
startTransition(() => {
// 关键:计算乐观状态,它会在过渡期间覆盖原始 state
const nextOptimisticState = updateFn
? updateFn(state, value)
: value;
setOptimisticState(nextOptimisticState);
});
};
// 注意:当过渡结束后,isPending 变为 false,currentOptimisticState 自动切回 state
return [currentOptimisticState, addOptimistic];
}