
useOptimistic()
useOptimistic()
useOptimistic 是一个 React Hook,它可以帮助你更乐观地更新用户界面。
js
const [optimisticState, setOptimistic] = useOptimistic(value, reducer?);
参考 {/reference/}
useOptimistic(value, reducer?) {/useoptimistic/}
在组件的顶层调用 useOptimistic 来创建一个乐观状态。
js
import { useOptimistic } from 'react';
function MyComponent({name, todos}) {
const [optimisticAge, setOptimisticAge] = useOptimistic(28);
const [optimisticName, setOptimisticName] = useOptimistic(name);
const [optimisticTodos, setOptimisticTodos] = useOptimistic(todos, todoReducer);
// ...
}
参数 {/parameters/}
value:当没有待处理的 Action 时返回的值。- 可选
reducer(currentState, action):指定乐观状态如何更新的 reducer 函数。它必须是纯函数,接收当前状态和 reducer 操作的参数,并返回下一个乐观状态。
返回值 {/returns/}
useOptimistic 返回一个包含两个值的数组:
optimisticState:当前的乐观状态。如果没有待处理的 Action,它等于value;否则等于reducer返回的状态(如果没有提供reducer,则等于传入 set 函数的值)。set函数:允许你在 Action 内部将乐观状态更新为不同的值。
set 函数,如 setOptimistic(optimisticState) {/setoptimistic/}
useOptimistic 返回的 set 函数允许你为 Action 的持续时间内更新状态。你可以直接传递下一个状态,或传入一个基于前一状态计算新状态的函数:
js
const [optimisticLike, setOptimisticLike] = useOptimistic(false);
const [optimisticSubs, setOptimisticSubs] = useOptimistic(subs);
function handleClick() {
startTransition(async () => {
setOptimisticLike(true);
setOptimisticSubs(a => a + 1);
await saveChanges();
});
}
参数 {/setoptimistic-parameters/}
optimisticState:你希望在 Action 期间乐观状态成为的值。如果你为useOptimistic提供了reducer,这个值将作为第二个参数传递给 reducer。它可以是任何类型的值。- 如果你将一个函数作为
optimisticState传入,它将被视为_更新函数_。它必须是纯函数,只接收待处理状态作为其唯一参数,并返回下一个乐观状态。React 会将你的更新函数放入队列,并重新渲染组件。在下一次渲染期间,React 会将排队的更新函数应用到上一个状态来计算下一个状态,类似于useState更新函数。
- 如果你将一个函数作为
返回值 {/setoptimistic-returns/}
set 函数没有返回值。
注意事项 {/setoptimistic-caveats/}
set函数必须在 Action 内部调用。如果你在 Action 外部调用 setter,React 将显示警告,乐观状态会短暂渲染。
乐观状态如何工作 {/how-optimistic-state-works/}
useOptimistic 允许你在 Action 进行期间显示临时值:
js
const [value, setValue] = useState('a');
const [optimistic, setOptimistic] = useOptimistic(value);
startTransition(async () => {
setOptimistic('b');
const newValue = await saveChanges('b');
setValue(newValue);
});
当 setter 在 Action 内被调用时,useOptimistic 会触发一次重新渲染,在 Action 进行期间显示该状态。否则,返回传入 useOptimistic 的 value。
这种状态被称为“乐观的”,是因为它被用来立即向用户展示执行 Action 的结果,即使该 Action 实际上需要时间才能完成。
更新流程
- 立即更新:当调用
setOptimistic('b')时,React 立即以'b'渲染。 - (可选)在 Action 中等待:如果你在 Action 中
await,React 会继续显示'b'。 - 调度 Transition:
setValue(newValue)调度一个对真实状态的更新。 - (可选)等待 Suspense:如果
newValue挂起,React 会继续显示'b'。 - 一次渲染提交:最后,
newValue提交到value和optimistic。
没有额外的渲染来“清除”乐观状态。当 Transition 完成时,乐观状态和真实状态会在同一次渲染中收敛。
乐观状态是临时的 {/optimistic-state-is-temporary/}
乐观状态只在 Action 进行期间渲染,否则渲染 value。
如果 saveChanges 返回了 'c',那么 value 和 optimistic 都将变为 'c',而不是 'b'。
最终状态如何确定
useOptimistic 的 value 参数决定了 Action 结束后显示的内容。其工作方式取决于你使用的模式:
- 硬编码值,如
useOptimistic(false):Action 结束后,state仍然是false,所以 UI 显示false。这对于总是从false开始的待处理状态很有用。 - 传入 props 或 state,如
useOptimistic(isLiked):如果父组件在 Action 期间更新了isLiked,Action 完成后会使用新值。这就是 UI 反映 Action 结果的方式。 - reducer 模式,如
useOptimistic(items, fn):如果items在 Action 待处理时发生变化,React 会使用新的items重新运行reducer来重新计算状态。这会使你的乐观添加保持基于最新数据之上。
Action 失败时会发生什么
如果 Action 抛出错误,Transition 仍然结束,React 会以当前 value 进行渲染。由于父组件通常只在成功时更新 value,失败意味着 value 没有改变,因此 UI 显示乐观更新之前的内容。你可以捕获错误并向用户显示消息。
用法 {/usage/}
向组件添加乐观状态 {/adding-optimistic-state-to-a-component/}
在组件顶层调用 useOptimistic 来声明一个或多个乐观状态。
js [[1, 4, "age"], [1, 5, "name"], [1, 6, "todos"], [2, 4, "optimisticAge"], [2, 5, "optimisticName"], [2, 6, "optimisticTodos"], [3, 4, "setOptimisticAge"], [3, 5, "setOptimisticName"], [3, 6, "setOptimisticTodos"], [4, 6, "reducer"]]
import { useOptimistic } from 'react';
function MyComponent({age, name, todos}) {
const [optimisticAge, setOptimisticAge] = useOptimistic(age);
const [optimisticName, setOptimisticName] = useOptimistic(name);
const [optimisticTodos, setOptimisticTodos] = useOptimistic(todos, reducer);
// ...
useOptimistic 返回一个包含两项的数组:
乐观状态 ,初始设置为提供的value 。set 函数 ,允许你在 Action 期间临时更改状态。- 如果提供了
reducer ,它会在返回乐观状态之前运行。
- 如果提供了
要使用 set 函数。
Action 是在 startTransition 内部调用的函数:
js {3}
function onAgeChange(e) {
startTransition(async () => {
setOptimisticAge(42);
const newAge = await postAge(42);
setAge(newAge);
});
}
React 会先渲染乐观状态 42,而 age 保持当前年龄。Action 等待 POST,然后为 age 和 optimisticAge 渲染 newAge。
参见 乐观状态如何工作 深入了解。
当使用 Action props 时,你可以无需 startTransition 直接调用 set 函数:
js [[3, 2, "setOptimisticName"]]
async function submitAction() {
setOptimisticName('Taylor');
await updateName('Taylor');
}
这是因为 Action props 已经在 startTransition 内部被调用。
有关示例,请参阅:在 Action props 中使用乐观状态。
在 Action props 中使用乐观状态 {/using-optimistic-state-in-action-props/}
在 Action prop 中,你可以直接调用乐观 setter,无需 startTransition。
此示例在 <form> 的 submitAction prop 中设置乐观状态:
js src/App.js
import { useState, startTransition } from 'react';
import EditName from './EditName';
export default function App() {
const [name, setName] = useState('Alice');
return <EditName name={name} action={setName} />;
}
js src/EditName.js active
import { useOptimistic, startTransition } from 'react';
import { updateName } from './actions.js';
export default function EditName({ name, action }) {
const [optimisticName, setOptimisticName] = useOptimistic(name);
async function submitAction(formData) {
const newName = formData.get('name');
setOptimisticName(newName);
const updatedName = await updateName(newName);
startTransition(() => {
action(updatedName);
})
}
return (
<form action={submitAction}>
<p>你的名字是:{optimisticName}</p>
<p>
<label>修改:</label>
<input
type="text"
name="name"
disabled={name !== optimisticName}
/>
</p>
</form>
);
}
js src/actions.js hidden
export async function updateName(name) {
await new Promise((res) => setTimeout(res, 1000));
return name;
}
在此示例中,当用户提交表单时,optimisticName 会立即更新,以在服务器请求进行期间乐观地显示 newName。当请求完成时,name 和 optimisticName 会使用响应中的实际 updatedName 渲染。
为什么这里不需要 startTransition? {/why-doesnt-this-need-starttransition/}
按照约定,在 startTransition 内部调用的 props 会被命名为“Action”。
因为 submitAction 以“Action”命名,你可以知道它已经在 startTransition 内部被调用。
参见 从组件暴露 action prop 了解 Action prop 模式。
向 Action props 添加乐观状态 {/adding-optimistic-state-to-action-props/}
在创建 Action prop 时,你可以添加 useOptimistic 来显示即时反馈。
这里有一个按钮,在 action 待处理时显示“提交中...”:
js src/App.js
import { useState, startTransition } from 'react';
import Button from './Button';
import { submitForm } from './actions.js';
export default function App() {
const [count, setCount] = useState(0);
return (
<div>
<Button action={async () => {
await submitForm();
startTransition(() => {
setCount(c => c + 1);
});
}}>增加</Button>
{count > 0 && <p>已提交 {count} 次!</p>}
</div>
);
}
js src/Button.js active
import { useOptimistic, startTransition } from 'react';
export default function Button({ action, children }) {
const [isPending, setIsPending] = useOptimistic(false);
return (
<button
disabled={isPending}
onClick={() => {
startTransition(async () => {
setIsPending(true);
await action();
});
}}
>
{isPending ? '提交中...' : children}
</button>
);
}
js src/actions.js hidden
export async function submitForm() {
await new Promise((res) => setTimeout(res, 1000));
}
当按钮被点击时,setIsPending(true) 使用乐观状态立即显示“提交中...”并禁用按钮。当 Action 完成时,isPending 会自动渲染为 false。
此模式会自动为 Button 的任何 action prop 显示待处理状态:
js
// 显示状态更新的待处理状态
<Button action={() => { setState(c => c + 1) }} />
// 显示导航的待处理状态
<Button action={() => { navigate('/done') }} />
// 显示 POST 请求的待处理状态
<Button action={async () => { await fetch(/* ... */) }} />
// 显示组合操作的待处理状态
<Button action={async () => {
setState(c => c + 1);
await fetch(/* ... */);
navigate('/done');
}} />
待处理状态会一直显示,直到 action prop 中的所有操作完成。
你也可以使用 useTransition 通过 isPending 获取待处理状态。
区别在于 useTransition 返回 startTransition 函数,而 useOptimistic 适用于任何 Transition。根据你组件的需求选择使用。
乐观地更新 props 或 state {/updating-props-or-state-optimistically/}
你可以将 props 或 state 包装在 useOptimistic 中,以便在 Action 进行期间立即更新它。
在此示例中,LikeButton 接收 isLiked 作为 prop,并在点击时立即切换它:
js src/App.js
import { useState, useOptimistic, startTransition } from 'react';
import { toggleLike } from './actions.js';
export default function App() {
const [isLiked, setIsLiked] = useState(false);
const [optimisticIsLiked, setOptimisticIsLiked] = useOptimistic(isLiked);
function handleClick() {
startTransition(async () => {
const newValue = !optimisticIsLiked
console.log('⏳ 设置乐观状态:' + newValue);
setOptimisticIsLiked(newValue);
const updatedValue = await toggleLike(newValue);
startTransition(() => {
console.log('⏳ 设置真实状态:' + updatedValue );
setIsLiked(updatedValue);
});
});
}
if (optimisticIsLiked !== isLiked) {
console.log('✅ 渲染乐观状态:' + optimisticIsLiked);
} else {
console.log('✅ 渲染真实值:' + optimisticIsLiked);
}
return (
<button onClick={handleClick}>
{optimisticIsLiked ? '❤️ 取消喜欢' : '🤍 喜欢'}
</button>
);
}
js src/actions.js hidden
export async function toggleLike(value) {
return await new Promise((res) => setTimeout(() => res(value), 1000));
// 在真实应用中,这会更新服务器
}
js src/index.js hidden
import React from 'react';
import {createRoot} from 'react-dom/client';
import './styles.css';
import App from './App';
const root = createRoot(document.getElementById('root'));
// 未使用 StrictMode,因此不会显示双重渲染日志。
root.render(<App />);
当按钮被点击时,setOptimisticIsLiked 立即更新显示的状态,将爱心显示为已喜欢。与此同时,await toggleLike 在后台运行。当 await 完成时,父组件的 setIsLiked 更新“真实”的 isLiked 状态,乐观状态也会渲染以匹配这个新值。
此示例读取 optimisticIsLiked 来计算下一个值。这适用于基础状态不会改变的情况,但如果你的基础状态在 Action 待处理时可能发生变化,你可能需要使用状态更新函数或 reducer。
参见 基于当前状态更新状态 查看示例。
一起更新多个值 {/updating-multiple-values-together/}
当乐观更新影响多个相关值时,使用 reducer 一起更新它们。这可以确保 UI 保持一致。
这是一个关注按钮,同时更新关注状态和粉丝数量:
js src/App.js
import { useState, startTransition } from 'react';
import { followUser, unfollowUser } from './actions.js';
import FollowButton from './FollowButton';
export default function App() {
const [user, setUser] = useState({
name: 'React',
isFollowing: false,
followerCount: 10500
});
async function followAction(shouldFollow) {
if (shouldFollow) {
await followUser(user.name);
} else {
await unfollowUser(user.name);
}
startTransition(() => {
setUser(current => ({
...current,
isFollowing: shouldFollow,
followerCount: current.followerCount + (shouldFollow ? 1 : -1)
}));
});
}
return <FollowButton user={user} followAction={followAction} />;
}
js src/FollowButton.js active
import { useOptimistic, startTransition } from 'react';
export default function FollowButton({ user, followAction }) {
const [optimisticState, updateOptimistic] = useOptimistic(
{ isFollowing: user.isFollowing, followerCount: user.followerCount },
(current, isFollowing) => ({
isFollowing,
followerCount: current.followerCount + (isFollowing ? 1 : -1)
})
);
function handleClick() {
const newFollowState = !optimisticState.isFollowing;
startTransition(async () => {
updateOptimistic(newFollowState);
await followAction(newFollowState);
});
}
return (
<div>
<p><strong>{user.name}</strong></p>
<p>{optimisticState.followerCount} 粉丝</p>
<button onClick={handleClick}>
{optimisticState.isFollowing ? '取消关注' : '关注'}
</button>
</div>
);
}
js src/actions.js hidden
export async function followUser(name) {
await new Promise((res) => setTimeout(res, 1000));
}
export async function unfollowUser(name) {
await new Promise((res) => setTimeout(res, 1000));
}
reducer 接收新的 isFollowing 值,并在单次更新中同时计算新的关注状态和更新后的粉丝数量。这确保了按钮文本和数量始终同步。
在更新函数和 reducer 之间选择 {/choosing-between-updaters-and-reducers/}
useOptimistic 支持两种基于当前状态计算状态的模式:
更新函数的工作方式类似于 useState 更新函数。将一个函数传递给 setter:
js
const [optimistic, setOptimistic] = useOptimistic(value);
setOptimistic(current => !current);
Reducers 将更新逻辑与 setter 调用分开:
js
const [optimistic, dispatch] = useOptimistic(value, (current, action) => {
// 基于 current 和 action 计算下一个状态
});
dispatch(action);
当 setter 调用自然地描述更新时,使用更新函数。 这类似于使用 useState 时使用 setState(prev => ...)。
当需要向更新传递数据(例如要添加的项)或需要在单个 hook 中处理多种更新类型时,使用 reducers。
为什么使用 reducer?
当你的 Transition 待处理期间基础状态可能发生变化时,reducers 是必不可少的。如果你的 todos 在添加待处理期间发生变化(例如,另一个用户添加了一个待办),React 会使用新的 todos 重新运行你的 reducer 来重新计算显示内容。这确保你的新待办被添加到最新列表,而不是过时的副本。
像 setOptimistic(prev => [...prev, newItem]) 这样的更新函数只会看到 Transition 开始时的状态,而会遗漏异步工作期间发生的任何更新。
乐观地添加到列表 {/optimistically-adding-to-a-list/}
当你需要乐观地向列表中添加项目时,使用 reducer:
js src/App.js
import { useState, startTransition } from 'react';
import { addTodo } from './actions.js';
import TodoList from './TodoList';
export default function App() {
const [todos, setTodos] = useState([
{ id: 1, text: '学习 React' }
]);
async function addTodoAction(newTodo) {
const savedTodo = await addTodo(newTodo);
startTransition(() => {
setTodos(todos => [...todos, savedTodo]);
});
}
return <TodoList todos={todos} addTodoAction={addTodoAction} />;
}
js src/TodoList.js active
import { useOptimistic, startTransition } from 'react';
export default function TodoList({ todos, addTodoAction }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(currentTodos, newTodo) => [
...currentTodos,
{ id: newTodo.id, text: newTodo.text, pending: true }
]
);
function handleAddTodo(text) {
const newTodo = { id: crypto.randomUUID(), text: text };
startTransition(async () => {
addOptimisticTodo(newTodo);
await addTodoAction(newTodo);
});
}
return (
<div>
<button onClick={() => handleAddTodo('新待办')}>添加待办</button>
<ul>
{optimisticTodos.map(todo => (
<li key={todo.id}>
{todo.text} {todo.pending && "(添加中...)"}
</li>
))}
</ul>
</div>
);
}
js src/actions.js hidden
export async function addTodo(todo) {
await new Promise((res) => setTimeout(res, 1000));
// 在真实应用中,这会保存到服务器
return { ...todo, pending: false };
}
reducer 接收当前待办列表和新添加的待办。这一点很重要,因为如果你的 todos prop 在添加待处理期间发生变化(例如,另一个用户添加了一个待办),React 会通过使用更新后的列表重新运行 reducer 来更新你的乐观状态。这确保你的新待办被添加到最新列表,而不是过时的副本。
每个乐观项包含一个 pending: true 标记,以便你可以为单个项显示加载状态。当服务器响应并且父组件用已保存的项更新规范的 todos 列表时,乐观状态会更新为已确认的项,而不带 pending 标记。
处理多种 action 类型 {/handling-multiple-action-types/}
当你需要处理多种类型的乐观更新(如添加和移除项目)时,使用 reducer 模式和 action 对象。
此购物车示例展示了如何使用一个 reducer 处理添加和移除:
js src/App.js
import { useState, startTransition } from 'react';
import { addToCart, removeFromCart, updateQuantity } from './actions.js';
import ShoppingCart from './ShoppingCart';
export default function App() {
const [cart, setCart] = useState([]);
const cartActions = {
async add(item) {
await addToCart(item);
startTransition(() => {
setCart(current => {
const exists = current.find(i => i.id === item.id);
if (exists) {
return current.map(i =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
);
}
return [...current, { ...item, quantity: 1 }];
});
});
},
async remove(id) {
await removeFromCart(id);
startTransition(() => {
setCart(current => current.filter(item => item.id !== id));
});
},
async updateQuantity(id, quantity) {
await updateQuantity(id, quantity);
startTransition(() => {
setCart(current =>
current.map(item =>
item.id === id ? { ...item, quantity } : item
)
);
});
}
};
return <ShoppingCart cart={cart} cartActions={cartActions} />;
}
js src/ShoppingCart.js active
import { useOptimistic, startTransition } from 'react';
export default function ShoppingCart({ cart, cartActions }) {
const [optimisticCart, dispatch] = useOptimistic(
cart,
(currentCart, action) => {
switch (action.type) {
case 'add':
const exists = currentCart.find(item => item.id === action.item.id);
if (exists) {
return currentCart.map(item =>
item.id === action.item.id
? { ...item, quantity: item.quantity + 1, pending: true }
: item
);
}
return [...currentCart, { ...action.item, quantity: 1, pending: true }];
case 'remove':
return currentCart.filter(item => item.id !== action.id);
case 'update_quantity':
return currentCart.map(item =>
item.id === action.id
? { ...item, quantity: action.quantity, pending: true }
: item
);
default:
return currentCart;
}
}
);
function handleAdd(item) {
startTransition(async () => {
dispatch({ type: 'add', item });
await cartActions.add(item);
});
}
function handleRemove(id) {
startTransition(async () => {
dispatch({ type: 'remove', id });
await cartActions.remove(id);
});
}
function handleUpdateQuantity(id, quantity) {
startTransition(async () => {
dispatch({ type: 'update_quantity', id, quantity });
await cartActions.updateQuantity(id, quantity);
});
}
const total = optimisticCart.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
return (
<div>
<h2>购物车</h2>
<div style={{ marginBottom: 16 }}>
<button onClick={() => handleAdd({
id: 1, name: 'T 恤', price: 25
})}>
添加 T 恤($25)
</button>{' '}
<button onClick={() => handleAdd({
id: 2, name: '马克杯', price: 15
})}>
添加马克杯($15)
</button>
</div>
{optimisticCart.length === 0 ? (
<p>你的购物车是空的</p>
) : (
<ul>
{optimisticCart.map(item => (
<li key={item.id}>
{item.name} - ${item.price} ×
{item.quantity}
{' '}= ${item.price * item.quantity}
<button
onClick={() => handleRemove(item.id)}
style={{ marginLeft: 8 }}
>
移除
</button>
{item.pending && ' ...'}
</li>
))}
</ul>
)}
<p><strong>总计:${total}</strong></p>
</div>
);
}
js src/actions.js hidden
export async function addToCart(item) {
await new Promise((res) => setTimeout(res, 800));
}
export async function removeFromCart(id) {
await new Promise((res) => setTimeout(res, 800));
}
export async function updateQuantity(id, quantity) {
await new Promise((res) => setTimeout(res, 800));
}
reducer 处理三种 action 类型(add、remove、update_quantity),并为每种类型返回新的乐观状态。每个 action 都会设置一个 pending: true 标记,以便你可以显示视觉反馈,同时 Server Function 正在运行。
带有错误恢复的乐观删除 {/optimistic-delete-with-error-recovery/}
当乐观地删除项目时,你应该处理 Action 失败的情况。
此示例展示了当删除失败时如何显示错误消息,并且 UI 会自动回滚以再次显示该项目。
js src/App.js
import { useState, startTransition } from 'react';
import { deleteItem } from './actions.js';
import ItemList from './ItemList';
export default function App() {
const [items, setItems] = useState([
{ id: 1, name: '学习 React' },
{ id: 2, name: '构建应用' },
{ id: 3, name: '部署到生产环境' },
]);
async function deleteAction(id) {
await deleteItem(id);
startTransition(() => {
setItems(current => current.filter(item => item.id !== id));
});
}
return <ItemList items={items} deleteAction={deleteAction} />;
}
js src/ItemList.js active
import { useState, useOptimistic, startTransition } from 'react';
export default function ItemList({ items, deleteAction }) {
const [error, setError] = useState(null);
const [optimisticItems, removeItem] = useOptimistic(
items,
(currentItems, idToRemove) =>
currentItems.map(item =>
item.id === idToRemove
? { ...item, deleting: true }
: item
)
);
function handleDelete(id) {
setError(null);
startTransition(async () => {
removeItem(id);
try {
await deleteAction(id);
} catch (e) {
setError(e.message);
}
});
}
return (
<div>
<h2>你的项目</h2>
<ul>
{optimisticItems.map(item => (
<li
key={item.id}
style={{
opacity: item.deleting ? 0.5 : 1,
textDecoration: item.deleting ? 'line-through' : 'none',
transition: 'opacity 0.2s'
}}
>
{item.name}
<button
onClick={() => handleDelete(item.id)}
disabled={item.deleting}
style={{ marginLeft: 8 }}
>
{item.deleting ? '删除中...' : '删除'}
</button>
</li>
))}
</ul>
{error && (
<p style={{ color: 'red', padding: 8, background: '#fee' }}>
{error}
</p>
)}
</div>
);
}
js src/actions.js hidden
export async function deleteItem(id) {
await new Promise((res) => setTimeout(res, 1000));
// 项目 3 总是失败以演示错误恢复
if (id === 3) {
throw new Error('无法删除。权限不足。');
}
}
尝试删除“部署到生产环境”。当删除失败时,该项目会自动重新出现在列表中。
故障排除 {/troubleshooting/}
我遇到了错误:“在 Transition 或 Action 之外发生了乐观状态更新” {/an-optimistic-state-update-occurred-outside-a-transition-or-action/}
你可能会看到此错误:
An optimistic state update occurred outside a Transition or Action. To fix, move the update to an Action, or wrap with startTransition.
乐观 setter 函数必须在 startTransition 内部调用:
js
// 🚩 错误:在 Transition 之外
function handleClick() {
setOptimistic(newValue); // 警告!
// ...
}
// ✅ 正确:在 Transition 内部
function handleClick() {
startTransition(async () => {
setOptimistic(newValue);
// ...
});
}
// ✅ 也正确:在 Action prop 内部
function submitAction(formData) {
setOptimistic(newValue);
// ...
}
当你在 Action 之外调用 setter 时,乐观状态会短暂出现,然后立即恢复为原始值。这是因为没有 Transition 来在 Action 运行时“保持”乐观状态。
我遇到了错误:“渲染时无法更新乐观状态” {/cannot-update-optimistic-state-while-rendering/}
你可能会看到此错误:
Cannot update optimistic state while rendering.
当你在组件渲染阶段调用乐观 setter 时,会发生此错误。你只能在事件处理器、effects 或其他回调中调用它:
js
// 🚩 错误:渲染期间调用
function MyComponent({ items }) {
const [isPending, setPending] = useOptimistic(false);
// 这会在渲染期间运行——不允许!
setPending(true);
// ...
}
// ✅ 正确:在 startTransition 内部调用
function MyComponent({ items }) {
const [isPending, setPending] = useOptimistic(false);
function handleClick() {
startTransition(() => {
setPending(true);
// ...
});
}
// ...
}
// ✅ 也正确:从 Action 调用
function MyComponent({ items }) {
const [isPending, setPending] = useOptimistic(false);
function action() {
setPending(true);
// ...
}
// ...
}
我的乐观更新显示过期值 {/my-optimistic-updates-show-stale-values/}
如果你的乐观状态似乎基于旧数据,请考虑使用更新函数或 reducer 来相对于当前状态计算乐观状态。
js
// 如果状态在 Action 期间发生变化,可能会显示过期数据
const [optimistic, setOptimistic] = useOptimistic(count);
setOptimistic(5); // 总是设置为 5,即使 count 已改变
// 更好:相对更新能正确处理状态变化
const [optimistic, adjust] = useOptimistic(count, (current, delta) => current + delta);
adjust(1); // 总是将当前 count 加 1
参见 基于当前状态更新状态 了解详情。
我不知道我的乐观更新是否在等待 {/i-dont-know-if-my-optimistic-update-is-pending/}
要了解 useOptimistic 是否处于待处理状态,你有三个选项:
- 检查
optimisticValue === value
js
const [optimistic, setOptimistic] = useOptimistic(value);
const isPending = optimistic !== value;
如果值不相等,则说明有 Transition 正在进行。
- 添加
useTransition
js
const [isPending, startTransition] = useTransition();
const [optimistic, setOptimistic] = useOptimistic(value);
//...
startTransition(() => {
setOptimistic(state);
})
由于 useTransition 在底层使用 useOptimistic 来计算 isPending,这与选项 1 等价。
- 在 reducer 中添加
pending标记
js
const [optimistic, addOptimistic] = useOptimistic(
items,
(state, newItem) => [...state, { ...newItem, isPending: true }]
);
由于每个乐观项都有自己的标记,你可以为单个项显示加载状态。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
