目录
本篇我们将学习, useOptimistic 是一个 React Hook,它可以帮助你更乐观地更新用户界面
什么是"乐观"更新用户界面
假设我们有这样一个场景:
用户发布了一条评论,随后点击「删除」按钮。
在乐观更新 的策略下,当用户点击删除时,评论会立即从页面中消失,而不是等待删除接口返回结果。
随后:
-
如果接口返回成功:
UI 已经是正确状态,无需额外处理;
-
如果接口返回失败:
再将这条评论恢复到页面中,并给出错误提示。
这种做法之所以被称为「乐观」更新,是因为前端在请求结果尚未确认之前,就乐观地假设操作一定会成功,并提前更新界面。
它的核心目标并不是"减少请求时间",而是提升用户对交互的即时反馈感知,让操作看起来"立刻生效"。
如果没有乐观更新是怎么样的
还是以上面的「删除评论」为例。
如果不使用乐观更新,当用户点击删除按钮后,前端通常会:
- 先进入 loading 状态;
- 等待删除接口返回结果;
- 接口成功后,才从页面中移除这条评论。
在这个过程中,用户会明显感知到延迟:按钮点了,但页面没有立刻变化,只能"等结果"。
举一反三
同理,就比如我们还一个场景,我们现在需要点赞,用户点击按钮后直接给点赞值加1,无需等待接口返回就更新UI,后续如果接口返回成功就不需要更新,如果失败我们就恢复原先状态,并给错误提示。
如何自己写一个乐观更新函数
我们用上面的删除评论场景, 整个过程是我们先保存旧状态,然后更新UI,接下来就是发请求,如果请求成功了,那么就不需要做什么,如果请求失败了,我们回滚旧状态,具体代码如下。
javascript
function CommentList() {
const [comments, setComments] = useState(initialComments)
function handleDelete(id) {
// 1️⃣ 保存旧状态
const prevComments = comments
// 2️⃣ 立即更新 UI(乐观更新)
setComments(comments.filter(c => c.id !== id))
// 3️⃣ 发请求
deleteCommentApi(id)
.then(() => {
// 成功:什么都不用做
})
.catch(() => {
// 4️⃣ 失败:回滚
setComments(prevComments)
alert('删除失败,已恢复')
})
}
return (
<>
{comments.map(c => (
<div key={c.id}>
{c.text}
<button onClick={() => handleDelete(c.id)}>删除</button>
</div>
))}
</>
)
}
目前我们这种写法已经能够实现单次操作的、简单场景的乐观更新,但是如果面对连续点击,复杂交互,无法处理多个乐观更新同时更新。
比如,我现在连续删除了A和B, 结果A的返回值是失败,B的返回值是成功,那么目前回滚会变成什么呢。
这样结果就是B已经成功删除了,A没有成功删除,结果B被回滚回去了。
因此react提供了useOptimistic 能帮助我们更好实现乐观更新。 他能够很好维护每一次乐观更新。
如何使用useOptimistic
javascript
const [optimisticState, setOptimistic] = useOptimistic(value, reducer?);
输入
value: 当没有待处理的操作时返回的值
reducer(currentState, action):reducer可传也可不传 这个参数用来描述触发一次乐观更新状态应该如何变化,用于一些复杂的计算
输出返回两个值
- optimisticState(当前乐观状态) 也就是我们显示在页面的值
- 在没有进行中的 Action 时,它等于你传入的原始
value - 当存在进行中的 Action 时,它等于:
reducer计算后的状态- 如果没有提供
reducer,则等于传给set函数的值
- 在没有进行中的 Action 时,它等于你传入的原始
- setOptimistic(更新函数) 更新页面值的函数
- 用于在 Action 内部 更新乐观状态
- 通过它可以临时将 UI 更新为一个"预期成功"的状态
案例
我们依然使用上面描述的删除评论的一个案例
javascript
import { useOptimistic, useState } from 'react';
function CommentList() {
const [comments, setComments] = useState(initialComments);
const [optimisticComments, setOptimisticComments] = useOptimistic(
comments,
(state, commentId) =>
state.filter(comment => comment.id !== commentId) //这里通过 filter 直接从列表中移除该评论
);
async function handleDelete(id) {
// 1️⃣ 立刻更新 UI(乐观)
setOptimisticComments(id);
try {
// 2️⃣ 请求接口
await fakeDeleteApi(id);
// 3️⃣ 接口成功,正式更新真实状态
setComments(prev =>
prev.filter(comment => comment.id !== id)
);
} catch (e) {
// 4️⃣ 接口失败,回滚(useOptimistic 自动恢复)
alert('删除失败,请重试');
}
}
return (
<ul>
{optimisticComments.map(comment => (
<li key={comment.id}>
{comment.content}
<button onClick={() => handleDelete(comment.id)}>
删除
</button>
</li>
))}
</ul>
);
}
总结
乐观更新的本质,是在用户操作发生的第一时间更新 UI ,而不是等待接口返回结果。
它通过先假设成功、失败再回滚 的方式,用可控的回滚成本 ,换取更流畅、更即时的用户交互体验。
以上如有错误可以评论,万分感谢!!!
Reference