Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • React

    • 废弃Create-React-App
    • ice.js中Model数据初始化原理
    • React19 useOptimistic hooks介绍
      • 与异步请求结合
      • 核心注意点
      • 完整Demo
      • 总结
  • Vue

  • JavaScript文章

  • 学习笔记

  • openlayers

  • threejs

  • MapboxGL

  • 工具

  • 源码合集

  • ECMAScript历年新特性

  • 前端
  • React
东流
2026-03-19
目录

React19 useOptimistic hooks介绍

当然可以!下面是一篇完整的关于 React 19 useOptimistic Hook 用法 的博客文章,已用 Markdown 格式整理,适合直接发布或作为学习笔记:

# React 19 新特性:`useOptimistic` Hook 完整指南

在 React 19 中,官方引入了一个全新的 Hook —— **`useOptimistic`**,用于处理 **乐观 UI 更新(Optimistic UI)**。  
它能够让你的应用在用户操作时立即更新 UI,而无需等待异步操作完成,从而提供更流畅的交互体验。

本文将深入讲解 `useOptimistic` 的用法,并提供示例代码。

---

## 什么是乐观更新?

乐观更新是一种常见的 UI 技术:

- 用户触发操作(例如点赞、收藏、发送消息)
- UI **立即反应**,不等待服务器响应
- 异步请求成功时,保持 UI 状态
- 异步请求失败时,回退或提示错误

传统实现方式通常需要手动管理临时状态和回退逻辑,而 `useOptimistic` 帮助我们更优雅地管理这一流程。

---

## `useOptimistic` 基本语法

```js
const [optimisticValue, updateOptimistic] = useOptimistic(initialValue, reducer);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  • initialValue:初始状态值
  • reducer(current, action):一个 reducer 函数,用于根据 action 更新乐观状态
  • optimisticValue:当前乐观状态
  • updateOptimistic(action):触发乐观状态更新

示例:简单计数器

import { useOptimistic } from 'react';

function Counter() {
  const [count, updateCount] = useOptimistic(0, (current, action) => current + action);

  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => updateCount(1)}>+1</button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12

点击按钮时,UI 会立即增加数字,而不依赖任何异步请求。


# 与异步请求结合

乐观更新的最大价值在于异步操作。 例如,我们模拟一个“点赞按钮”,并假设请求有 50% 概率失败:

import { useState, useTransition, useOptimistic } from 'react';

export default function LikeButton() {
  const [likes, setLikes] = useState(0);
  const [optimisticLikes, addOptimisticLikes] = useOptimistic(likes, (current, action) => current + action);
  const [isPending, startTransition] = useTransition();
  const [error, setError] = useState('');

  const handleLike = () => {
    setError('');
    addOptimisticLikes(1); // 乐观更新

    startTransition(async () => {
      try {
        await new Promise(r => setTimeout(r, 1000)); // 模拟延迟
        const success = Math.random() < 0.5; // 50% 概率失败
        if (!success) throw new Error('服务器请求失败');

        setLikes(prev => prev + 1); // 成功更新真实状态
      } catch (err) {
        setError(err.message);
        addOptimisticLikes(-1); // 回退乐观值
      }
    });
  };

  return (
    <div style={{ fontFamily: 'sans-serif', padding: 20 }}>
      <p>点赞数: {optimisticLikes}</p>
      <button onClick={handleLike} disabled={isPending}>
        {isPending ? '处理中...' : '点赞'}
      </button>
      {error && <p style={{ color: 'red' }}>⚠️ {error}</p>}
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 核心注意点

  1. 乐观状态与真实状态分离

    • optimisticValue 是暂时的 UI 状态
    • setState 才是最终真实状态
  2. 失败回退需要手动处理

    • React 不知道服务器请求是否失败
    • 必须在 catch 中使用 reducer 回退乐观值
  3. 支持多次并发更新

    • useOptimistic 内部会管理队列,多个乐观更新不会互相覆盖
  4. 与 useTransition 搭配使用

    • 可以控制异步更新的优先级
    • 提供加载状态(isPending)优化 UX

# 完整Demo

import React, {
  useState,
  useOptimistic,
  useCallback,
  useTransition,
} from "react";

// 类型定义
interface LikeButtonProps {
  id: string;
  initialLikes: number;
}

interface ApiResponse {
  likes: number;
  success: boolean;
}

function LikeButton({ id, initialLikes }: LikeButtonProps) {
  // 实际状态
  const [likes, setLikes] = useState<number>(initialLikes);

  // 乐观更新状态
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likes,
    (currentLikes: number, action: { type: "like" | "unlike" | "reset" }) => {
      switch (action.type) {
        case "like":
          return currentLikes + 1;
        case "unlike":
          return Math.max(0, currentLikes - 1);
        case "reset":
          return initialLikes;
        default:
          return currentLikes;
      }
    },
  );

  const [error, setError] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState<boolean>(false);

  // ✅ 必须使用 useTransition
  const [isPending, startTransition] = useTransition();

  // 模拟 API 调用
  const simulateApiCall = async (
    action: "like" | "unlike" | "reset",
    currentLikes: number,
  ): Promise<ApiResponse> => {
    console.log("Starting API call:", action);
    await new Promise((resolve) => setTimeout(resolve, 2000));

    if (Math.random() < 0.2) {
      console.log("API call failed");
      throw new Error("网络请求失败,请重试");
    }

    switch (action) {
      case "like":
        return { likes: currentLikes + 1, success: true };
      case "unlike":
        return { likes: Math.max(0, currentLikes - 1), success: true };
      case "reset":
        return { likes: initialLikes, success: true };
    }
  };

  // ============================================
  // ✅ 修复:用 startTransition 包裹乐观更新
  // ============================================
  const handleLike = useCallback(async () => {
    console.log("=== Handle Like clicked ===");

    setError(null);
    setIsLoading(true);

    const previousLikes = likes;

    // 关键:把乐观更新放在 startTransition 中
    startTransition(() => {
      console.log("1. Optimistic update inside transition");
      addOptimisticLike({ type: "like" });
    });
    startTransition(async () => {
      try {
        console.log("2. Starting API call...");
        const result = await simulateApiCall("like", previousLikes);
        console.log("3. API success, updating real state");
        setLikes(result.likes);
      } catch (err) {
        console.log("4. API failed, rolling back");
        // 回滚:同样要在 transition 中
        setLikes(previousLikes);

        setError(err instanceof Error ? err.message : "未知错误");
      } finally {
        setIsLoading(false);
      }
    });
  }, [likes]);

  const handleUnlike = useCallback(async () => {
    setError(null);
    setIsLoading(true);

    const previousLikes = likes;

    startTransition(() => {
      addOptimisticLike({ type: "unlike" });
    });
    startTransition(async () => {
      try {
        const result = await simulateApiCall("unlike", previousLikes);
        setLikes(result.likes);
      } catch (err) {
        setLikes(previousLikes);
        setError(err instanceof Error ? err.message : "未知错误");
      } finally {
        setIsLoading(false);
      }
    });
  }, [likes]);

  const handleReset = useCallback(async () => {
    setError(null);
    setIsLoading(true);

    const previousLikes = likes;

    startTransition(() => {
      addOptimisticLike({ type: "reset" });
    });

    try {
      const result = await simulateApiCall("reset", previousLikes);
      setLikes(result.likes);
    } catch (err) {
      startTransition(() => {
        setLikes(previousLikes);
      });
      setError(err instanceof Error ? err.message : "未知错误");
    } finally {
      setIsLoading(false);
    }
  }, [likes, initialLikes]);

  const clearError = () => setError(null);

  return (
    <div
      style={{
        padding: "30px",
        border: "2px solid #e0e0e0",
        borderRadius: "12px",
        maxWidth: "600px",
        margin: "0 auto",
        fontFamily: "system-ui, -apple-system, sans-serif",
      }}
    >
      <h2 style={{ textAlign: "center", color: "#333", marginBottom: "30px" }}>
        useOptimistic 完整示例(React 19.2 修复版)
      </h2>

      {/* 状态对比区域 */}
      <div
        style={{
          display: "flex",
          justifyContent: "space-around",
          margin: "30px 0",
          padding: "20px",
          backgroundColor: "#f8f9fa",
          borderRadius: "8px",
        }}
      >
        <div style={{ textAlign: "center" }}>
          <h3
            style={{ color: "#2196f3", marginBottom: "10px", fontSize: "14px" }}
          >
            实际状态 (Real State)
          </h3>
          <div style={{ fontSize: "48px", fontWeight: "bold", color: "#333" }}>
            {likes}
          </div>
          <p style={{ color: "#666", marginTop: "5px", fontSize: "12px" }}>
            来自服务器的真实数据
          </p>
        </div>

        <div style={{ textAlign: "center" }}>
          <h3
            style={{ color: "#ff9800", marginBottom: "10px", fontSize: "14px" }}
          >
            乐观状态 (Optimistic State)
          </h3>
          <div
            style={{
              fontSize: "48px",
              fontWeight: "bold",
              color: optimisticLikes !== likes ? "#ff9800" : "#4caf50",
              transition: "color 0.3s",
            }}
          >
            {optimisticLikes}
          </div>
          <p style={{ color: "#666", marginTop: "5px", fontSize: "12px" }}>
            立即更新的 UI 状态
          </p>
        </div>
      </div>

      {/* 状态指示器 */}
      {optimisticLikes !== likes && !error && (
        <div
          style={{
            padding: "15px",
            backgroundColor: "#fff3e0",
            borderRadius: "8px",
            marginBottom: "20px",
            textAlign: "center",
          }}
        >
          <span style={{ color: "#ff9800", fontWeight: "bold" }}>
            ⚡ 乐观更新中... 等待服务器响应
          </span>
        </div>
      )}

      {/* 操作按钮区域 */}
      <div
        style={{
          display: "flex",
          justifyContent: "center",
          gap: "15px",
          margin: "30px 0",
        }}
      >
        <button
          onClick={handleLike}
          disabled={isLoading}
          style={{
            padding: "15px 30px",
            fontSize: "16px",
            fontWeight: "bold",
            cursor: isLoading ? "not-allowed" : "pointer",
            backgroundColor: isLoading ? "#ccc" : "#2196f3",
            color: "white",
            border: "none",
            borderRadius: "8px",
          }}
        >
          {isLoading ? "⏳ 处理中..." : `👍 点赞`}
        </button>

        <button
          onClick={handleUnlike}
          disabled={isLoading || likes === 0}
          style={{
            padding: "15px 30px",
            fontSize: "16px",
            fontWeight: "bold",
            cursor: isLoading || likes === 0 ? "not-allowed" : "pointer",
            backgroundColor: isLoading || likes === 0 ? "#ccc" : "#f44336",
            color: "white",
            border: "none",
            borderRadius: "8px",
          }}
        >
          {isLoading ? "⏳ 处理中..." : "👎 取消点赞"}
        </button>

        <button
          onClick={handleReset}
          disabled={isLoading}
          style={{
            padding: "15px 30px",
            fontSize: "16px",
            fontWeight: "bold",
            cursor: isLoading ? "not-allowed" : "pointer",
            backgroundColor: isLoading ? "#ccc" : "#9c27b0",
            color: "white",
            border: "none",
            borderRadius: "8px",
          }}
        >
          {isLoading ? "⏳ 处理中..." : "🔄 重置"}
        </button>
      </div>

      {/* 错误提示 */}
      {error && (
        <div
          style={{
            padding: "15px",
            backgroundColor: "#ffebee",
            borderRadius: "8px",
            marginBottom: "20px",
            textAlign: "center",
          }}
        >
          <span style={{ color: "#c62828", fontWeight: "bold" }}>
            ❌ {error}
          </span>
        </div>
      )}

      {/* 说明 */}
      <div
        style={{
          marginTop: "20px",
          padding: "15px",
          backgroundColor: "#e3f2fd",
          borderRadius: "8px",
        }}
      >
        <p style={{ margin: 0, color: "#333", fontSize: "14px" }}>
          <strong>💡 React 19.2 更新:</strong>useOptimistic 必须在
          useTransition 中使用!
        </p>
      </div>
    </div>
  );
}

export default LikeButton;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325

# 总结

useOptimistic 是 React 19 提供的 乐观 UI 管理工具,它可以:

  • 立即更新 UI
  • 合并多个乐观更新
  • 与异步操作无缝配合

适合以下场景:

  • 点赞 / 收藏 / 评论
  • 表单提交预览
  • 实时数据交互(如聊天、投票)

乐观更新不仅提高了响应速度,还让应用体验更加流畅。


🎯 小贴士

  • 异步操作失败时,务必回退乐观值
  • 多次乐观更新时,确保 reducer 是幂等的
  • 可以结合 useTransition 显示加载状态或禁用按钮

React 19 的 useOptimistic 提供了一种优雅的方式管理乐观 UI,是前端异步交互的利器。

编辑 (opens new window)
上次更新: 2026/03/20, 05:45:01
ice.js中Model数据初始化原理
vue-demi介绍

← ice.js中Model数据初始化原理 vue-demi介绍→

最近更新
01
completeWork方法解析
03-11
02
completeUnitOfWork方法解析
02-12
03
beginWork方法解析
02-06
更多文章>
Theme by Vdoing | Copyright © 2024-2026 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式