852 字
4 分钟
useOptimistic

它解决什么#

点了「点赞」,请求要一秒才回来。这一秒界面不动,用户会以为没点上,于是再点一次。

乐观更新把顺序倒过来:先把结果画上去,等服务器确认后再对齐。用户体验上是「立刻生效」。

基本用法#

import { useOptimistic, startTransition } from 'react'
function LikeButton({ isLiked, onChange }) {
const [optimisticLiked, setOptimisticLiked] = useOptimistic(isLiked)
function handleClick() {
startTransition(async () => {
setOptimisticLiked(!optimisticLiked)
const saved = await toggleLike(!optimisticLiked)
startTransition(() => onChange(saved))
})
}
return (
<button onClick={handleClick}>
{optimisticLiked ? '取消赞' : '点赞'}
</button>
)
}

签名是 useOptimistic(value, reducer?),返回 [optimisticState, setOptimistic]。没有动作在跑时,乐观值就等于 value。

它的生命期#

乐观值只活在一次过渡期间。过渡结束后,真实值与乐观值在同一次渲染里对齐,没有额外的「清掉乐观值」那一步。

动作失败时,过渡照样结束,界面渲染的是 value 当前的值。因为父组件通常只在成功后才更新 value,失败看起来就是自动回滚。想提示用户,就在动作里 catch 住错误、自己存一条消息(这类错误属于可预期错误,不要抛出去,见 useActionState)。

setter 必须在动作里调用#

直接调用会报「An optimistic state update occurred outside a Transition or Action」,乐观值一闪就回退,因为没有过渡替你「托住」它。

两个合法位置:包在 startTransition 里;或者写在 action prop 里,action prop 本来就在过渡中执行。

同样不要在渲染期间调用,那会报「Cannot update optimistic state while rendering」。

会同时变多个值就用 reducer#

const [state, updateOptimistic] = useOptimistic(
{ isFollowing: user.isFollowing, count: user.followerCount },
(current, isFollowing) => ({
isFollowing,
count: current.count + (isFollowing ? 1 : -1),
})
)

按钮文案与粉丝数必须一起变,拆成两个状态就容易对不上。

reducer 还有一个更硬的理由:动作进行期间,基础值可能被别处改掉。例如你正在添加一条待办时,别人也往列表里加了一条。React 会用最新的基础值重跑 reducer,让你的乐观改动加在最新数据上。

换成「直接设一个值」就会出问题:setOptimistic(5) 无论当前是多少都写 5,会盖掉期间发生的其他更新。凡是需要基于当前值计算的,都用更新函数或 reducer。

怎么知道还在进行中#

三种办法,按需要挑:

// 1. 乐观值与真实值不一致,说明有动作在跑
const isPending = optimisticLiked !== isLiked
// 2. 另外用 useTransition 拿 isPending
const [isPending, startTransition] = useTransition()
// 3. 在 reducer 里给每条数据打标记,适合列表逐项显示
const [items, addItem] = useOptimistic(list, (current, item) => [
...current,
{ ...item, pending: true },
])

第三种最细:列表里新加的那一条可以单独显示「添加中……」。

四条边界#

它不发起请求,也不管请求,只负责「这一小段时间里先显示什么」。请求本身还是要写在动作函数里。

不要把乐观值当真值用。别把它写进缓存、别拿它算最终金额,它随时可能被真实值替换。

只在能预判结果、失败也能回滚的操作上用。不可撤销的改动(写库、扣款)用乐观更新要慎重:界面回滚了,服务端未必也回滚了。

它的更新是临时的,所以别拿它替代状态管理。真正的数据源仍然是 value 那一侧。

第 33 篇,共 39 篇本文属于系列

useOptimistic
https://www.choria.top/posts/hook-use-optimistic/
作者
乔瑞雅·克林威尔
发布于
2026-05-02
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 190 天

部分内容可能已过时