React 19 useOptimistic:构建即时 UI,无需等待服务器

发布: (2026年4月21日 GMT+8 08:36)
4 分钟阅读
原文: Dev.to

I’m happy to translate the article for you, but I’ll need the text you’d like translated. Could you please paste the content (or the portion you want translated) here? I’ll keep the source line and all formatting exactly as you requested.

Introduction

useOptimistic 是 React 19 中最少被使用的 Hook 之一。与其依赖本地状态加加载指示器,不如在服务器请求后台进行时,直接推送即时的 UI 更新,让界面感觉更流畅。

使用 useOptimistic

旧方法 – 用户会看到延迟

async function addTodo(text: string) {
  setLoading(true);
  const newTodo = await createTodo(text); // 200‑800 ms 等待
  setTodos(prev => [...prev, newTodo]);
  setLoading(false);
}

使用 useOptimistic

const [optimisticTodos, addOptimisticTodo] = useOptimistic(
  todos,
  (currentTodos, newText: string) => [
    ...currentTodos,
    { id: crypto.randomUUID(), text: newText, pending: true }
  ]
);

async function addTodo(text: string) {
  addOptimisticTodo(text);               // 即时 UI 更新
  const newTodo = await createTodo(text); // 后台请求
  setTodos(prev => [...prev, newTodo]);
}

条目会立即出现。如果请求失败,乐观更新会自动回滚。

核心 API

const [optimisticState, dispatchOptimistic] = useOptimistic(
  state,      // real state (from server/parent)
  updateFn    // (currentState, action) => nextOptimisticState
);

当异步操作完成时(无论成功还是抛出异常),React 会自动将 optimisticState 恢复为 state。无需手动回滚。

示例:Todo 列表

function TodoList({ todos }: { todos: Todo[] }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, text: string) => [
      ...state,
      { id: crypto.randomUUID(), text, completed: false, pending: true }
    ]
  );

  async function formAction(formData: FormData) {
    addOptimisticTodo(formData.get("todo") as string);
    await saveTodo(formData.get("todo") as string);
  }

  return (
    <>
      {optimisticTodos.map(todo => (
        <div key={todo.id}>
          {todo.text}
        </div>
      ))}

      <form onSubmit={e => {
        e.preventDefault();
        const data = new FormData(e.currentTarget);
        formAction(data);
      }}>
        <input name="todo" />
        <button type="submit">Add</button>
      </form>
    </>
  );
}

示例:点赞切换

const [optimistic, dispatch] = useOptimistic(
  { liked, count },
  (current) => ({
    liked: !current.liked,
    count: current.liked ? current.count - 1 : current.count + 1,
  })
);

async function handleLike() {
  dispatch(null);               // Instant toggle
  const result = await toggleLike(postId);
  setLike(result);
}

处理失败

useOptimistic 只在异步操作 抛出 时回滚。静默失败会导致乐观状态卡住。

错误模式 – 未抛出

async function save(data) {
  const json = await fetch(...).then(r => r.json());
  if (!json.success) return json.error; // returns, doesn't throw
}

正确模式 – 出错时抛出

async function save(data) {
  const res = await fetch(...);
  if (!res.ok) throw new Error(`Failed: ${res.status}`);
  return res.json();
}

带错误处理的示例

async function submitComment(formData: FormData) {
  const text = formData.get("comment") as string;
  setError(null);
  addOptimisticComment(text);

  try {
    const comment = await postComment(postId, text);
    setComments(prev => [...prev, comment]);
  } catch {
    setError("Failed to post. Try again.");
    // useOptimistic auto-reverts the optimistic entry
  }
}

常见陷阱

  • 服务器大幅更改 – 如果服务器响应用显著不同的数据替换乐观占位符,界面可能会闪烁。
  • 高冲突场景 – 协同编辑、库存计数或财务写入可能导致频繁回滚。
  • 不可逆操作 – 删除、收费或已发送的电子邮件应在应用乐观更新前确认。

何时使用 useOptimistic

  • 添加、点赞、切换、重新排序、软删除。
  • 任何 CRUD 操作,只要感知延迟(即 UI 显示结果的时间)是用户的主要关注点。

useOptimistic 并不会让服务器更快;它通过在服务器响应之前显示结果,让应用感觉更快。

资源

  • React 19 + Next.js Server Actions + optimistic UI – AI SaaS Starter Kit 已经预先配置好所有内容,帮助您省去繁琐的设置。
0 浏览
Back to Blog

相关文章

阅读更多 »

一次性掌握解构

什么是解构 解构是一个 JavaScript 表达式,允许你以简洁的方式从数组、对象、maps 和 sets 中提取值。相反…