React19 新增Hooks:useOptimistic

useOptimistic

概念

官方文档上介绍:是一个可以帮助你更乐观地更新用户界面 的Hooks ,实际上 有点像 loading 的过渡效果,在异步操作的时候先显示一个乐观的结果(即我们提前指定的内容),

如果异步操作成功 ,再更新成我们预期的内容,如果异步操作失败,那么之前乐观的结果需要被撤销,用户界面要恢复到异步操作开始之前的状态。这样用户界面看起来会更流畅,用户体验更好。

例如,用户在文章下方发表评论,当点击发送按钮时,界面会立即显示为已发送状态,并将评论内容添加到评论列表中,不需要等待服务器的响应,这个时候显示的是一个乐观的结果。如果评论发送成功,那么之前乐观更新的状态会被保留,界面将继续显示为已发送状态,评论内容也会保持在评论列表中。如果评论发送失败,那么之前乐观更新的状态需要被撤销。界面需要回滚到未发送状态,并从评论列表中移除刚才添加的评论内容。

我觉得有一部分有点类似错误边界的感觉,但错误边界 是出现错误之后才显示备用内容,而 useOptimistic 是异步结果返回之前显示乐观内容。

用法

js 复制代码
const [optimisticState, addOptimistic] = useOptimistic(
    state,
    // 更新函数
    (currentState, optimisticValue) => {
      // 使用乐观值
      // 合并并返回新 state
    }
  );

state:是初始的状态,也就是乐观结果之前的状态
updateFn:是更新函数
参数

当前 state (即currentState ) 和传递给 addOptimistic 的值 (即optimisticValue),
返回值
optimisticState:如果异步操作开始或成功,等于currentState 和 optimisticValue 的合并值,其他情况下(不成功或者挂起)都等于state
addOptimistic:触发 optimisticState 更新的函数,通过调用addOptimistic 进而调用内部的 updateFn

效果

useOptimistic Hooks实现效果

js 复制代码
//App.js
import { useOptimistic, useState, useRef, startTransition } from "react";

// 模拟异步请求函数
const mockCommentRequest = async (message) => {
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      // 模拟请求成功或失败
      const isSuccess = Math.random() > 0.5;
      if (isSuccess) {
        resolve({ success: true, message: '成功' });
      } else {
        reject({ success: false, message: '失败' });
      }
    }, 1000);
  });
  return message;
};

function Thread({ messages, sendMessageAction }) {
  const formRef = useRef();

  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) => {
      // console.log(state, newMessage,'-------------')
      return [
        {
          text: newMessage,
          sending: true
        },
        ...state,
      ]
    }
  );

  function formAction(formData) {
    const message = formData.get("message");
    // 不管异步结果,先进行乐观更新
    addOptimisticMessage(message);
    formRef.current.reset();
    //执行异步操作
    startTransition(async () => {
      await sendMessageAction(message);
    });
  }

  return (
    <>
      <form action={formAction} ref={formRef}>
        <input type="text" name="message" placeholder="发送评论" />
        <button type="submit">发送</button>
      </form>
      {optimisticMessages.map((message, index) => (
        <div key={index}>
          {message.text}
          {message.sending && <small>(发送中......)</small>}
        </div>
      ))}
    </>
  );
}

export default function App() {
  const [messages, setMessages] = useState([
    { text: "评论1", sending: false, key: 1 },
    { text: "评论2", sending: false, key: 2 },
  ]);

  async function sendMessageAction(message) {
    try {
      const sentMessage = await mockCommentRequest(message);
      startTransition(() => {
        setMessages((messages) => [
          { text: sentMessage, sending: false },
          ...messages
        ]);
      });
    } catch (error) {
      console.error('发送失败:', error.message);
      // 可以在这里处理发送失败的情况,例如显示错误提示
    }
  }

  return <Thread messages={messages} sendMessageAction={sendMessageAction} />;
}

思考

1. 在这个Hooks出现之前,我们如何实现类似的效果的呢?

useOptimistic 之前,我们需要手动管理状态来模拟乐观更新和回滚。在实现上,首先把数据添加到显示数组,实现乐观更新,如果异步操作成功,替换成真实发送的消息,如果失败,从显示数组中移除乐观消息,相当于手动回滚到操作前的状态。

js 复制代码
function Thread({ messages, sendMessageAction }) {
  const formRef = useRef();
  const [displayMessages, setDisplayMessages] = useState(messages);

  async function formAction(formData) {
    const message = formData.get("message");
    const tempId = Date.now();
    
    // 1. 乐观更新
    setDisplayMessages(prev => [
      { id: tempId, text: message, sending: true },
      ...prev
    ]);
    formRef.current.reset();

    try {
      // 2. 实际发送
      const sentMessage = await sendMessageAction(message);
      
      // 3. 替换乐观消息为实际消息
      setDisplayMessages(prev => [
        { text: sentMessage, sending: false },
        ...prev.filter(msg => msg.id !== tempId)
      ]);
    } catch (error) {
      // 4. 失败时移除乐观消息
      setDisplayMessages(prev => prev.filter(msg => msg.id !== tempId));
      console.error('发送失败:', error.message);
    }
  }

  return (
    <>
      <form action={formAction} ref={formRef}>
        <input type="text" name="message" placeholder="发送评论" />
        <button type="submit">发送</button>
      </form>
      {displayMessages.map((message,index) => (
        <div key={index}>
          {message.text}
          {message.sending && <small>(发送中......)</small>}
        </div>
      ))}
    </>
  );
}

2. useOptimistic 相比于之前的实现 有什么优势吗?

  1. 简化状态管理:可以帮助我们自动回滚到原来的状态,不需要手动回滚
  2. 逻辑分离:把乐观更新的逻辑和异步操作的逻辑分离,使得代码结构更清晰。开发者可以专注于异步操作的实现,而不必过多关注状态管理的细节。
相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax