什么是useOptimistic
useOptimistic 是React 19引入的新hook,用于实现乐观更新,允许我们在等待异步操作完成时立即更新UI,让用户对异步无感,提供更好的用户体验。
为什么需要useOptimistic
在一些场景下,需要在异步请求完成前立即更新UI来提升用户体验,如点赞、收藏等操作。useOptimistic简化了乐观更新的实现,在useOptimistic之前想要实现乐观更新,我们需要大量额外的代码判断控制更新流程和改变状态。useOptimistic能够根据我们是否调用addOptimistic来决定返回的组件状态实现UI切换。
基本使用
useOptimistic 接收一个state和updateFn函数,返回optimisticState和addOptimistic函数。
state:初始状态
updateFn:更新函数,接收state和optimisticValue,返回结果乐观状态,乐观状态是state和optimisticValue的合并值。必须是纯函数。
optimisticState:结果乐观状态,如果有操作挂起,它为state和optimisticValue的合并值,否则为state
addOptimistic:触发乐观更新时调用的dispatch函数,接收optimisticValue,并调用updateFn,触发更新
import { useOptimistic, useState, useRef, startTransition } from "react";
interface Message {
text: string;
sending: boolean;
key: number;
}
async function deliverMessage(message:string) {
await new Promise((res) => setTimeout(res, 1000));
return message;
}
function Thread({ messages, sendMessageAction }: { messages: Message[], sendMessageAction: (formData: FormData) => Promise<void> }) {
const formRef = useRef<HTMLFormElement>(null);
function formAction(formData: FormData) {
addOptimisticMessage(formData.get("message") as string);
formRef.current?.reset();
startTransition(async () => {
await sendMessageAction(formData);
});
}
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage: string) => [
{
text: newMessage,
sending: true,
key: state.length + 1
},
...state,
]
);
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: "你好,在这儿!", sending: false, key: 1 }
]);
async function sendMessageAction(formData: FormData) {
const sentMessage = await deliverMessage(formData.get("message") as string);
startTransition(() => {
setMessages((messages) => [{ text: sentMessage, sending: false, key: messages.length+1 }, ...messages]);
})
}
return <Thread messages={messages} sendMessageAction={sendMessageAction} />;
}