Optimistic UI در React بدون باگهای رایج
Optimistic UI یعنی رابط کاربری قبل از تمامشدن درخواست سرور، نتیجهای را نشان دهد که انتظار داریم موفق شود. این الگو برای Like، تغییر وضعیت، اضافهکردن آیتم و عملیات کوتاه بسیار مفید است؛ اما اگر state خوشبینانه را با state واقعی اشتباه بگیریم، خیلی زود با rollback ناقص، داده تکراری و race condition روبهرو میشویم.
در React، useOptimistic برای ساخت یک نمای موقت از state طراحی شده است. نکته مهم این است که سرور همچنان منبع حقیقت باقی میماند.
1. اول مشخص کنید چه چیزی واقعاً Optimistic است
هر Mutation مناسب Optimistic UI نیست. عملیات مناسب معمولاً نتیجه قابل پیشبینی، شکست نادر و rollback قابلفهم دارد. برای انتقال پول، ثبت سفارش حساس یا عملیاتی که سرور ممکن است نتیجهای کاملاً متفاوت بسازد، بهتر است UI را بیش از حد خوشبینانه نکنید.
2. State واقعی را دوباره نسازید
import { startTransition, useOptimistic } from "react"
type Comment = { id: string; body: string; pending?: boolean }
function Comments({ comments }: { comments: Comment[] }) {
const [optimisticComments, addOptimisticComment] = useOptimistic(
comments,
(current, draft: Comment) => [...current, draft],
)
async function submit(body: string) {
const temporary = { id: crypto.randomUUID(), body, pending: true }
startTransition(async () => {
addOptimisticComment(temporary)
await createComment({ body })
})
}
return optimisticComments.map((comment) => (
<article key={comment.id} aria-busy={comment.pending}>{comment.body}</article>
))
}
useOptimistic یک state موقت برای دوره انجام Action میسازد. وقتی داده واقعی جدید برسد، UI باید دوباره با منبع حقیقت همگام شود.
3. شناسه موقت را با شناسه سرور یکی نگیرید
اگر سرور id تولید میکند، برای آیتم pending یک شناسه موقت مستقل بسازید. بعد از موفقیت، داده authoritative سرور باید جای نسخه موقت را بگیرد. نگهداشتن همزمان آیتم optimistic و append کردن پاسخ سرور یکی از علتهای رایج duplicate است.
4. شکست را بخشی از طراحی UI بدانید
rollback فقط برگرداندن مقدار نیست. کاربر باید بفهمد چه اتفاقی افتاده و در صورت امکان بتواند دوباره تلاش کند. برای فرمهای طولانی، حذف دائمی یا عملیات چندمرحلهای، نگهداشتن draft کاربر معمولاً بهتر از پاککردن همهچیز بعد از خطاست.
5. Mutationهای همزمان را جدی بگیرید
اگر کاربر چند Action پشتسرهم اجرا کند، ترتیب پاسخها ممکن است با ترتیب کلیکها یکی نباشد. مشخص کنید Actionها مستقلاند، آخرین درخواست باید برنده باشد، سرور version authoritative دارد یا باید Action تکراری را موقتاً غیرفعال کرد.
6. Transition جای Error Handling نیست
Transition به React کمک میکند آپدیت غیرضروری برای تعامل فوری را مدیریت کند؛ اما مسئول retry، rollback یا اعتبارسنجی پاسخ سرور نیست.
User intent
↓
Optimistic projection
↓
Server mutation
↓
Success → authoritative data
Failure → feedback / rollback / retry
7. با Server State Library مرز روشنی داشته باشید
اگر داده با TanStack Query یا ابزار مشابه مدیریت میشود، دو سیستم optimistic مستقل برای یک Resource نسازید. یک Feature باید یک مالک روشن برای lifecycle داده داشته باشد.
چکلیست نهایی
قبل از انتشار بررسی کنید سرور منبع حقیقت مانده، pending state مشخص است، شکست feedback دارد، پاسخ سرور duplicate نمیسازد، Actionهای همزمان تست شدهاند و state موازی غیرضروری وجود ندارد.
Optimistic UI زمانی خوب است که latency را پنهان کند، نه حقیقت سیستم را. هرچه reconciliation پیچیدهتر میشود، دوباره بررسی کنید که آیا این عملیات اصلاً باید optimistic باشد یا نه.