useOptimistic
useOptimistic
帮助你更乐观地更新用户界面。
useOptimistic
和 之前的参数列表要保持一致,不然会导致最终数据可能不一致- 添加乐观更新数据时,会手动设置 loading 值,再从 api 拿到数据后会进行数据重置,
不要忘记
- 这样当数据加载成功或者失败,都会同步在
optimisticTodos
数据,这样就实现了,错误重置,成功则隐藏 loading 提示
const TodoList = () => {
const [todos, setTodos] = useState<any[]>([]);
const [optimisticTodos, addOptimistic] = useOptimistic(
todos,
(state, newData) => [newData, ...state]
);
// apiFn
const apiFn = async (pervState: any, formData: FormData) => {
// 获取到 todo 元素
const todo = formData.get("todo") as string;
// 验证
if (!todo) {
return {
...pervState,
todos: [...pervState.todos],
error: "todo is required",
success: false,
};
}
// 乐观更新
addOptimistic({
id: `temp-${Date.now()}`,
text: todo,
optimistic: true,
});
// 🔥 关键修复:直接在这里处理异步操作并返回状态
try {
// 添加到服务器 这里进行模拟 2s
const newTodo = await new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟 90% 成功率
if (Math.random() > 0.1) {
resolve({
id: Date.now(),
text: todo,
});
} else {
reject(new Error("发送失败"));
}
}, 2000);
});
console.log(newTodo);
// 成功后更新实际的 todos 状态
setTodos((prevTodos) => [newTodo as any, ...prevTodos]);
return {
...pervState,
todos: [newTodo as any, ...pervState.todos],
error: null,
success: true,
};
} catch (error) {
console.log(error);
return {
...pervState,
todos: [...pervState.todos],
error: "发送失败",
success: false,
};
}
};
// actionState
const [state, formAction, isPending] = useActionState(apiFn, {
todos: [],
error: null,
success: true,
});
console.log(state);
const handleAddTodo = (formData: FormData) => {
formAction(formData);
};
return (
<div className='todo-container'>
<div className='todo-form-section'>
<form action={handleAddTodo} className='todo-form'>
<div className='input-group'>
<input
type='text'
name='todo'
placeholder='输入待办事项...'
className='todo-input'
required
/>
<button type='submit' className='add-button' disabled={isPending}>
{isPending ? "添加中..." : "添加"}
</button>
</div>
</form>
</div>
<div className='todo-list-section'>
<h4>待办事项列表</h4>
{optimisticTodos.length === 0 ? (
<div className='empty-state'>
<p>暂无待办事项</p>
<span>快来添加第一个吧!</span>
</div>
) : (
<ul className='todo-list'>
{[...optimisticTodos].map((todo, index) => (
<li
key={todo.id || index}
className={`todo-item ${todo.optimistic ? "optimistic" : ""}`}
>
<div className='todo-content'>
<span className='todo-text'>{todo.text}</span>
{todo.optimistic && (
<span className='processing-indicator'>
<span className='dot'></span>
<span className='dot'></span>
<span className='dot'></span>
处理中...
</span>
)}
</div>
</li>
))}
</ul>
)}
</div>
{state?.error && <div className='error-message'>❌ {state.error}</div>}
</div>
);
};