
它解决什么
点了「点赞」,请求要一秒才回来。这一秒界面不动,用户会以为没点上,于是再点一次。
乐观更新把顺序倒过来:先把结果画上去,等服务器确认后再对齐。用户体验上是「立刻生效」。
基本用法
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 拿 isPendingconst [isPending, startTransition] = useTransition()
// 3. 在 reducer 里给每条数据打标记,适合列表逐项显示const [items, addItem] = useOptimistic(list, (current, item) => [ ...current, { ...item, pending: true },])第三种最细:列表里新加的那一条可以单独显示「添加中……」。
四条边界
它不发起请求,也不管请求,只负责「这一小段时间里先显示什么」。请求本身还是要写在动作函数里。
不要把乐观值当真值用。别把它写进缓存、别拿它算最终金额,它随时可能被真实值替换。
只在能预判结果、失败也能回滚的操作上用。不可撤销的改动(写库、扣款)用乐观更新要慎重:界面回滚了,服务端未必也回滚了。
它的更新是临时的,所以别拿它替代状态管理。真正的数据源仍然是 value 那一侧。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
最后更新于 ,距今已过 190 天
部分内容可能已过时