当前位置: 首页 > news >正文

React19 新增Hooks:useOptimistic

useOptimistic

概念

官方文档上介绍:是一个可以帮助你更乐观地更新用户界面 的Hooks,实际上 有点像 loading 的过渡效果,在异步操作的时候先显示一个乐观的结果(即我们提前指定的内容),
如果异步操作成功,再更新成我们预期的内容,如果异步操作失败,那么之前乐观的结果需要被撤销,用户界面要恢复到异步操作开始之前的状态。这样用户界面看起来会更流畅,用户体验更好。

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

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

用法

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

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

效果

useOptimistic Hooks实现效果

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

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. 逻辑分离:把乐观更新的逻辑和异步操作的逻辑分离,使得代码结构更清晰。开发者可以专注于异步操作的实现,而不必过多关注状态管理的细节。
http://www.dtcms.com/a/268805.html

相关文章:

  • 巧借东风:32位栈迁移破解ciscn_2019_es_2的空间困局
  • maven 发布到中央仓库-01-概览
  • 23、企业租赁管理(Rent)全流程指南:从资产盘活到价值最大化的数字化实践
  • Dify工作流实战:输入接口名,自动生成带源码的Markdown API文档(附完整Prompt)
  • Linux 文件系统与日志分析(补充)
  • 报错 400 和405解决方案
  • 海外短剧系统开发:PC端与H5端的全栈实践与深度解析
  • Day07- 管理并发和并行挑战:竞争条件和死锁
  • 在bash shell 函数传递数组的问题2
  • 【DeepSeek实战】17、MCP地图服务集成全景指南:高德、百度、腾讯三大平台接入实战
  • PCIE Ack/Nak机制详解
  • Unity 实现与 Ollama API 交互的实时流式响应处理
  • ES 压缩包安装
  • socket接口api的深度探究
  • 初识Neo4j之Cypher
  • 【Unity笔记】Unity 粒子系统 Triggers 使用解析:监听粒子进入与离开区域并触发事件
  • 在 macOS 上安装和测试 LibreOffice
  • 深入解析TCP:可靠传输的核心机制与实现逻辑(三次握手、四次挥手、流量控制、滑动窗口、拥塞控制、慢启动、延时应答、面向字节流、粘包问题)
  • 借助HarmonyOS SDK,《NBA巅峰对决》实现“分钟级启动”到“秒级进场”
  • 【7】PostgreSQL 事务
  • SRAM与三级缓存(L1/L2/L3 Cache)的关系
  • 芯谷科技--高性能双运算放大器D358
  • 第二届云计算与大数据国际学术会议(ICCBD 2025)
  • 火山引擎Data Agent全面上线售卖!以企业级数据智能体,重构数据应用范式
  • PostgreSQL中的HASH分区:原理、实现与最佳实践
  • 查看WPS Ofice是64位还是32位
  • 腾讯云 CDN 不支持 WebSocket 的现状与华为云 CDN 的替代方案-优雅草卓伊凡
  • 缺乏项目进度追踪工具,如何选择适合的工具
  • 中电金信 :十问高质量数据集:金融大模型价值重塑有“据”可循
  • 案例分享:应用VIC-3D High-Speed FFT进行吉他拨弦振动的工作变形ODS测量