React 中的乐观更新:从手写回滚到 useOptimistic

目录

什么是"乐观"更新用户界面

如果没有乐观更新是怎么样的

举一反三

如何自己写一个乐观更新函数

如何使用useOptimistic

案例

总结

本篇我们将学习, useOptimistic 是一个 React Hook,它可以帮助你更乐观地更新用户界面

什么是"乐观"更新用户界面

假设我们有这样一个场景:

用户发布了一条评论,随后点击「删除」按钮。

在乐观更新 的策略下,当用户点击删除时,评论会立即从页面中消失,而不是等待删除接口返回结果。

随后:

  • 如果接口返回成功:

    UI 已经是正确状态,无需额外处理;

  • 如果接口返回失败:

    再将这条评论恢复到页面中,并给出错误提示。

这种做法之所以被称为「乐观」更新,是因为前端在请求结果尚未确认之前,就乐观地假设操作一定会成功,并提前更新界面。

它的核心目标并不是"减少请求时间",而是提升用户对交互的即时反馈感知,让操作看起来"立刻生效"。

如果没有乐观更新是怎么样的

还是以上面的「删除评论」为例。

如果不使用乐观更新,当用户点击删除按钮后,前端通常会:

  1. 先进入 loading 状态;
  2. 等待删除接口返回结果;
  3. 接口成功后,才从页面中移除这条评论。

在这个过程中,用户会明显感知到延迟:按钮点了,但页面没有立刻变化,只能"等结果"。

举一反三

同理,就比如我们还一个场景,我们现在需要点赞,用户点击按钮后直接给点赞值加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 函数的值
  • 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

useOptimistic -- React 中文文档

相关推荐
10年前端老司机2 小时前
Next.js+LangGraph.js+ 简历工具AI Agent完整落地
前端·langchain·agent
IT_陈寒7 小时前
Java中equals方法比了个寂寞?原来这才是正确的重写姿势
前端·人工智能·后端
默_笙7 小时前
🚓 分诊台与拆题术:让 RAG 学会判断和规划
前端·javascript
CopyCode7 小时前
用 AI 迁项目有多爽?我把 Webpack 迁 Vite 的全过程记下来了
前端·架构
去伪存真7 小时前
Electron 自动化发布指南:GitHub Actions 跨平台打包全纪录
前端·electron
计算机魔术师7 小时前
OpenAI智能体失控闯进美国政府网站,53张用户图片外泄背后
前端
颜进强7 小时前
14 · NestJS ExecutionContext 执行上下文:守卫、拦截器、过滤器拿到的"同一个 context",为什么能力不一样?
前端·后端·ai编程
程序员Flycan7 小时前
🚀 跨域终结者:前端代理服务器(Proxy)原理解析与配置总结
前端
怕浪猫7 小时前
顶级模型一句话,AI 写出了能玩的 QQ飞车
前端·面试·github
huakoh7 小时前
MCP 报错分不清?先看响应里是 result 还是 error
前端