React useOptimistic乐观UI无法即时更新问题求助
问题:useOptimistic乐观更新延迟生效,无法即时更新UI
我正在开发一个React组件,尝试用useOptimistic实现乐观UI更新。没有真实后端,用模拟数据源messagesOnApi加载到本地状态messagesRetrievedFromApi后传入useOptimistic。预期添加新消息时UI立即乐观更新,同时模拟2秒API延迟,但实际乐观更新要等延迟结束才生效,达不到即时效果。
原代码
import { useOptimistic, useState, useTransition, useEffect } from "react"; import { Message, messagesOnApi } from "./MessagesData"; export function OptimisticMessages() { const [messagesRetrievedFromApi, setMessagesRetrievedFromApi] = useState<Message[]>([]); const [message, setMessage] = useState(""); const [optimisticMessages, addOptimisticMessage] = useOptimistic<Message[], string>( messagesRetrievedFromApi, (currentMessages, message) => { const finalMessages = [ ...currentMessages, { id: getNextId(currentMessages), message }, ]; return finalMessages; } ); const [_isPending, startTransition] = useTransition(); useEffect(() => { const data = JSON.parse(JSON.stringify(messagesOnApi)); setMessagesRetrievedFromApi(data); }, []); const addMessage = async (message: string) => { addOptimisticMessage(message); await new Promise((res) => setTimeout(() => { messagesOnApi.push({ id: getNextId(messagesOnApi), message, }); res(null); }, 2000) ); const data = JSON.parse(JSON.stringify(messagesOnApi)); setMessagesRetrievedFromApi(data); setMessage(""); }; const onClick = async () => { startTransition(() => { addMessage(message); }); }; return ( <div> <input value={message} onChange={(e) => setMessage(e.target.value)} /> <button onClick={onClick}>Add</button> <ul> {optimisticMessages.map((m, i) => ( <li key={i}>{m.message}</li> ))} </ul> </div> ); } function getNextId(currentMessages: Message[]) { let id = 0; for (let i = 0; i < currentMessages.length; i++) { if (currentMessages[i].id > id) id = currentMessages[i].id; } return id + 1; }
问题原因
你把包含异步逻辑的addMessage函数放在了startTransition的回调中,但startTransition的回调不应包含异步操作。React会将startTransition回调内的状态更新标记为过渡更新,而异步函数会让React等待整个异步流程完成后才处理其中的状态更新,直接导致addOptimisticMessage的执行被2秒延迟阻塞,无法触发即时的乐观UI更新。
修复方案
将addOptimisticMessage的调用移出startTransition,确保它能立即执行触发乐观更新;再用startTransition包裹后续的异步API模拟和真实状态更新逻辑,避免阻塞UI:
修改后的代码
import { useOptimistic, useState, useTransition, useEffect } from "react"; import { Message, messagesOnApi } from "./MessagesData"; export function OptimisticMessages() { const [messagesRetrievedFromApi, setMessagesRetrievedFromApi] = useState<Message[]>([]); const [message, setMessage] = useState(""); const [optimisticMessages, addOptimisticMessage] = useOptimistic<Message[], string>( messagesRetrievedFromApi, (currentMessages, message) => { const finalMessages = [ ...currentMessages, { id: getNextId(currentMessages), message }, ]; return finalMessages; } ); const [_isPending, startTransition] = useTransition(); useEffect(() => { const data = JSON.parse(JSON.stringify(messagesOnApi)); setMessagesRetrievedFromApi(data); }, []); const addMessage = async (message: string) => { await new Promise((res) => setTimeout(() => { messagesOnApi.push({ id: getNextId(messagesOnApi), message, }); res(null); }, 2000) ); const data = JSON.parse(JSON.stringify(messagesOnApi)); setMessagesRetrievedFromApi(data); setMessage(""); }; const onClick = () => { if (!message.trim()) return; // 立即触发乐观更新,不放入startTransition addOptimisticMessage(message); // 异步逻辑和真实状态更新放入startTransition,避免阻塞UI startTransition(async () => { await addMessage(message); }); }; return ( <div> <input value={message} onChange={(e) => setMessage(e.target.value)} /> <button onClick={onClick}>Add</button> <ul> {optimisticMessages.map((m) => ( <li key={m.id}>{m.message}</li> ))} </ul> </div> ); } function getNextId(currentMessages: Message[]) { let id = 0; for (let i = 0; i < currentMessages.length; i++) { if (currentMessages[i].id > id) id = currentMessages[i].id; } return id + 1; }
额外优化点
- 将列表的
key从索引i改为消息的id,符合React最佳实践,避免列表更新时的渲染异常 - 增加空内容判断,防止添加空消息
内容的提问来源于stack exchange,提问作者Webdevp
相关产品推荐
相关产品推荐

