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 相比于之前的实现 有什么优势吗?
- 简化状态管理:可以帮助我们自动回滚到原来的状态,不需要手动回滚
- 逻辑分离:把乐观更新的逻辑和异步操作的逻辑分离,使得代码结构更清晰。开发者可以专注于异步操作的实现,而不必过多关注状态管理的细节。