Menu
flag Ar iconالعربيةdown icon

الخطاف useOptimistic في React: واجهة فورية مع التراجع

يعرض useOptimistic النتيجة التي تتوقعها بينما ما زال إجراء غير متزامن يعمل، ثم يعود إلى الحالة الحقيقية عندما ينتهي، فيتراجع الطلب الفاشل من تلقاء نفسه. تعلّم الصيغة، وشكل المختزل، ولماذا يجب أن يعمل داخل إجراء.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

useOptimistic خطاف في React 19 يتيح للشاشة عرض نتيجة إجراء قبل أن ينتهي. تضبط قيمة متفائلة داخل إجراء؛ فتعرضها React ما دام الإجراء معلقًا، ثم تعود إلى الحالة الحقيقية. وإذا فشل الطلب، فالحالة الحقيقية لم تتغير أبدًا، فتتراجع الشاشة من تلقاء نفسها.

الواجهة البرمجية الوهمية في هذا المثال بديل عن طلب حقيقي وتفشل في كل استدعاء ثالث، لترى النتيجتين.

انقر الزر ثلاث مرات، مع انتظار ثانية بين النقرات. تتبدل الأوليان فورًا ويلحق سطر الحفظ بعد ثانية. والثالثة تتبدل فورًا أيضًا، ثم تنقلب عائدة عندما يفشل الطلب. غيّر calls % 3 إلى calls % 2 فتفشل كل نقرة ثانية.

الصيغة

const [optimisticState, setOptimistic] = useOptimistic(state, updateFn?);
  • state هي القيمة الحقيقية، عادة من useState أو props أو useActionState. وعندما لا يكون أي إجراء معلقًا، تكون optimisticState هذه القيمة بالضبط.
  • تضبط setOptimistic(value) القيمة المتفائلة ما دام الإجراء الحالي يعمل.
  • updateFn اختيارية: (currentState, optimisticValue) => nextState. معها تستقبل setOptimistic تغييرًا بدلًا من قيمة جديدة كاملة. انظر شكل المختزل أدناه.

دورة حياة النقرة الواحدة هي نفسها دائمًا:

  1. داخل إجراء، تستدعي setOptimistic. تتحدث الشاشة فورًا.
  2. ينتظر الإجراء الطلب.
  3. عند النجاح تحدّث الحالة الحقيقية (داخل startTransition عندما يأتي ذلك بعد await).
  4. ينتهي الإجراء. تتبع optimisticState الحالة state من جديد: القيمة المحفوظة عند النجاح، والقيمة القديمة عند الفشل.

الخطوة 4 هي سبب عدم وجود كود تراجع في المثال. لا تضبط كتلة catch إلا رسالة خطأ.

يجب أن يعمل داخل إجراء

لا توجد القيمة المتفائلة إلا ما دام انتقال معلقًا، لذا يجب استدعاء setOptimistic داخل واحد. وكل هذه تُحسب:

  • الدالة التي تمررها إلى startTransition (أو startTransition المأخوذة من useTransition)،
  • دالة تُمرَّر إلى <form action={...}> أو <button formAction={...}>،
  • الإجراء الذي تعطيه لـ useActionState.

إذا استُدعيت من onClick عادي، فليس للقيمة المتفائلة إجراء تعيش فيه. تعيدها React فورًا، وفي التطوير تسجّل React "An optimistic state update occurred outside a transition or action".

ينبغي تغليف تحديثات الحالة الحقيقية التي تحدث بعد await في startTransition خاصة بها، كما هو حال startTransition(() => setLiked(next)) أعلاه. لا تستطيع React أن تعرف، بعد await، أنك ما زلت داخل الانتقال السابق.

قائمة رسائل بشكل المختزل

عندما تكون الحالة المتفائلة قائمة، مرّر دالة تحديث كوسيط ثانٍ. عندها تستقبل دالة الضبط العنصر الجديد، وتُلحقه React بما تكون عليه القائمة الحقيقية في تلك اللحظة.

أرسل ثلاث رسائل بنصوص مختلفة، واحدة تلو الأخرى. تظهر كل منها باهتة مع "(sending...)" وتصبح واضحة بعد التسليم؛ وتختفي الثالثة من المحادثة وتظهر على أنها "Not sent". أرسلها بسرعة بدلًا من ذلك فتبقى الثلاث باهتة حتى ينتهي آخر طلب، لأن React تحتفظ بالقيم المتفائلة حتى تنتهي كل الإجراءات المعلقة. وإجراء النموذج انتقال أصلًا، فلا تحتاج addOptimistic هنا إلى startTransition خاصة بها.

أعطِ كل عنصر متفائل مفتاحًا لن يتعارض مع العناصر الحقيقية. يستخدم المثال pending- مع النص، فإرسال النص نفسه مرتين وكلاهما معلق سيتعارض؛ وتطبيق حقيقي سينشئ معرّفًا على العميل.

عدة تحديثات معًا

لأن دالة التحديث تستقبل الحالة الحالية، تتراكم التحديثات المتفائلة. انقر بسرعة فتُطبَّق كل نقرة معلقة فوق السابقة.

انقر أربع مرات بسرعة. تُظهر السلة 4 فورًا. وعندما تنتهي الطلبات، يستقر العدد على 3، لأن الطلب الثالث فشل ووحدة التحكم تقول ذلك. تحتفظ React بالقيم المتفائلة حتى تنتهي كل الإجراءات المعلقة، ثم تعرض العدد الحقيقي.

متى تستخدمه، ومتى لا

تناسب التحديثات المتفائلة الإجراءات التي تنجح دائمًا تقريبًا ويسهل التراجع عنها: الإعجابات، والنجوم، والمبدّلات، وإعادة التسمية، وإضافة رسالة. لا يرى المستخدم مؤشرًا دوارًا في الحالة الشائعة.

تجنّبها حيث تكون عبارة «تم» الزائفة مضللة: المدفوعات، وحذف حساب، وأي شيء قد يتصرف المستخدم بناءً عليه قبل معرفة النتيجة. لهذه، اعرض حالة انتظار بـ isPending من useActionState أو useTransition وانتظر الجواب الحقيقي.

أخبر المستخدم دائمًا عندما يحدث تراجع. القيمة التي تعود بصمت تبدو كخطأ برمجي. يحتفظ المثالان أعلاه برسالة خطأ في حالة عادية لتعيش بعد الإجراء.

مع useActionState

يتلاءم useOptimistic وuseActionState معًا. الإجراء المأخوذ من useActionState انتقال أصلًا، فيمكنك ضبط القيمة المتفائلة في بدايته، والحالة المُرجعة هي القيمة الحقيقية التي تعود إليها المتفائلة.

const [state, formAction] = useActionState(async (previous, formData) => {
    const title = formData.get('title');
    setOptimisticTitle(title);
    const saved = await saveTitle(title);
    return { title: saved.title };
}, { title: 'Untitled' });

const [optimisticTitle, setOptimisticTitle] = useOptimistic(state.title);

أثناء عمل الحفظ تعرض الصفحة العنوان الجديد. وعندما يُحل، تحمل state.title القيمة المحفوظة. وإذا رمت saveTitle خطأ، يذهب خطأ الإجراء إلى أقرب حدود أخطاء؛ ولعرض العنوان القديم مع رسالة بدلًا من ذلك، التقط الخطأ داخل الإجراء وأرجع الحالة السابقة مع خطأ.

أخطاء شائعة

استدعاء دالة الضبط خارج إجراء. لا تبقى القيمة المتفائلة على الشاشة، وتحذّر React في التطوير. غلّفها في startTransition أو انقلها إلى إجراء نموذج.

نسيان تحديث الحالة الحقيقية. عند النجاح، يجب أن يغيّر الإجراء الحالة التي يعكسها useOptimistic (setLiked وsetMessages). وإلا اختفت القيمة المتفائلة عندما ينتهي الإجراء، وبدا الطلب الناجح كتراجع.

تحديث الحالة الحقيقية بعد await دون انتقال. ما زال التحديث يصل، لكن React قد تعرضه في لحظة مختلفة عن نهاية الإجراء. غلّفه في startTransition، كما تفعل الأمثلة الثلاثة.

استخدام القيمة المتفائلة كمصدر للحقيقة. أرسل الطلبات واحسب المجاميع من الحالة الحقيقية. القيمة المتفائلة للعرض فقط ويمكن رميها في أي وقت.

الأسئلة الشائعة

ماذا يفعل useOptimistic؟

يعطيك نسخة من قطعة حالة يمكنك تغييرها فورًا أثناء عمل إجراء غير متزامن. وعندما ينتهي الإجراء، تعود النسخة إلى اتباع الحالة الحقيقية، التي تحمل حينها إما النتيجة المحفوظة أو القيمة القديمة.

كيف يتراجع useOptimistic عند الخطأ؟

لا يحتاج إلى مسار خاص للأخطاء. القيمة المتفائلة لا تعيش إلا ما دام الإجراء معلقًا. إذا فشل الطلب ولم تحدّث الحالة الحقيقية أبدًا، يعرض الخطاف الحالة الحقيقية من جديد، وهي القيمة التي كانت قبل النقر.

لماذا تظهر لي الرسالة "An optimistic state update occurred outside a transition or action"؟

استُدعيت دالة الضبط من معالج حدث عادي. استدعِها داخل دالة تُمرَّر إلى startTransition، أو داخل <form action>، أو داخل إجراء من useActionState.

ما هو الوسيط الثاني في useOptimistic؟

دالة تحديث اختيارية، (currentState, optimisticValue) => newState، مثل المختزل. معها تستقبل دالة الضبط التغيير فقط (رسالة جديدة، أو +1) وتحسب React الحالة المتفائلة، حتى عندما تكون عدة تحديثات معلقة في الوقت نفسه.

هل useOptimistic للنماذج فقط؟

لا. يعمل في أي انتقال، فالزر الذي يستدعي startTransition(async () => { ... }) يستطيع استخدامه كما يستطيع إجراء النموذج.

رسم توضيحي للغات البرمجة في Coddy

تعلّم البرمجة مع Coddy

ابدأ الآن