You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 22:42:44