Skip to main content

useOptimistic 乐观更新

Summary

useOptimistic 关联一个 state, 可以直接读取、操作其派生的值和action, 乐观更新状态, 不阻塞页面, 当真正的状态state 更新后, 该hook会合并更新

Example

'use client'

import { useRef, useState, startTransition, useOptimistic } from 'react'
import { addTodo } from '@/app/test/ToDo/action'
import { Button } from '@/components/ui'

export interface IToDo {
name: string
pending?: boolean
}

export function ToDo() {
const formRef = useRef<HTMLFormElement>(null)
const [todos, setTodos] = useState<IToDo[]>([])

const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo: IToDo) => {
console.log('乐观更新', newTodo)
return [...state, { ...newTodo, pending: true }]
},
)

async function formAction(formData: FormData) {
const name = formData.get('todo') as string
if (!name) return
const todo = {
name,
}

console.log('invoke addOptimisticTodo')
// 乐观更新
addOptimisticTodo(todo)
formRef.current?.reset()

// 提交到服务端
const res = await addTodo(todo)
startTransition(() => {
console.log('实际更新', res)
setTodos((prev) => [...prev, res]) // ✅ 函数式更新
})
}

return (
<>
<form action={formAction} ref={formRef}>
<input name='todo' type='text' placeholder='please input todo' />
<Button type='submit'>添加</Button>
</form>
<ul>
{optimisticTodos.map((todo, index) => (
<li key={`${todo}-${index}`}>
{todo.name} {todo.pending ? 'pending' : 'done'}
</li>
))}
</ul>
</>
)
}