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 中文文档

相关推荐
mCell5 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell6 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭6 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清6 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木7 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076607 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声7 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易7 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得07 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
anOnion7 小时前
构建无障碍组件之Dialog Pattern
前端·html·交互设计