تجعل التحديثات التفاؤلية لواجهة المستخدم في React التطبيقات تبدو فورية. تعلّم أنماطًا آمنة للمطابقة مع حقيقة الخادم، التعامل مع الفشل، ومنع انحراف البيانات.

واجهة المستخدم التفاؤلية في React تعني أنك تحدّث الشاشة كما لو أن التغيير قد نجح بالفعل، قبل أن يؤكد الخادم ذلك. شخص يضغط "إعجاب"، فيقفز العداد فورًا، ويتم إرسال الطلب في الخلفية.
هذا الاستجابة الفورية تجعل التطبيق يبدو سريعًا. على شبكة بطيئة، غالبًا ما تكون الفاصلة بين "سلس" و"هل تم؟".
المقابل هو انحراف البيانات: ما يراه المستخدم قد يتوقف تدريجيًا عن التطابق مع الحقيقة على الخادم. يظهر الانحراف عادةً كتناقضات صغيرة ومزعجة تعتمد على التوقيت ويصعب تكرارها.
يميل المستخدمون إلى ملاحظة الانحراف عندما "تغير الأمور رأيها" لاحقًا: عداد يقفز ثم يعود، عنصر يظهر ويختفي بعد التحديث، تعديل يبدو ثابتًا حتى تعود للصفحة، أو علامتا تبويب تُظهران قيمًا مختلفة.
يحدث ذلك لأن الواجهة تقدم تخمينًا، وقد يستجيب الخادم بحقيقة مختلفة. قواعد التحقق، وإلغاء التكرار، وفحوصات الصلاحيات، والحدود المعدّلة، أو جهاز آخر يغيّر نفس السجل يمكن أن يغيّر النتيجة النهائية. سبب شائع آخر هو الطلبات المتداخلة: استجابة أقدم تصل أخيرًا وتكتب فوق فعل المستخدم الأحدث.
مثال: تعيد تسمية مشروع إلى "Q1 Plan" وتعرضه فورًا في العنوان. قد يزيل الخادم المسافات، يرفض أحرفًا، أو يولّد slug مختلف. إن لم تستبدل القيمة التفاؤلية بقيمة الخادم النهائية، سيبدو الواجهة صحيحة حتى التحديث التالي عندما يتغيّر كل شيء "بشكل غامض".
الواجهة التفاؤلية ليست دائمًا الخيار الصحيح. كن حذرًا (أو تجنّبها) بالنسبة للمال والفوترة، والإجراءات غير القابلة للتراجع، وتغييرات الأدوار والصلاحيات، وسير العمل ذو قواعد خادم معقّدة، أو أي شيء له آثار جانبية تحتاج موافقة صريحة من المستخدم.
إذا اُستخدمت جيدًا، تجعل التحديثات التفاؤلية التطبيق يبدو فوريًا، لكن فقط إذا خططت للمطابقة، والترتيب، والتعامل مع الفشل.
تعمل الواجهة التفاؤلية بشكل أفضل عندما تفرق بين نوعين من الحالة:
معظم الانحراف يبدأ عندما يُعامَل التخمين المحلي كحقيقة مؤكدة.
قاعدة بسيطة: إذا كانت القيمة لها معنى تجاري خارج الشاشة الحالية، فالمصدر هو الخادم. إذا كانت تؤثر فقط على سلوك الشاشة (مفتوح أو مغلق، مدخل مُركز، نص مسودة)، فاحتفظ بها محليًا.
عمليًا، احتفظ بحقيقة الخادم لأشياء مثل الصلاحيات، والأسعار، والأرصدة، والمخزون، والحقول المحسوبة أو التي تتطلب تحققًا، وكل ما يمكن أن يتغير في مكان آخر (تبويب آخر، مستخدم آخر). احتفظ بحالة واجهة المستخدم المحلية للمسودات، وعلامات "قيد التحرير"، والفلاتر المؤقتة، والصفوف الموسعة، ومفاتيح الرسوم المتحركة.
بعض الإجراءات "آمنة للتخمين" لأن الخادم يقبلها غالبًا ويسهل عكسها، مثل تمييز عنصر بنجمة أو تبديل تفضيل بسيط.
عندما لا يكون الحقل آمنًا للتخمين، لا تزال تستطيع جعل التطبيق يبدو سريعًا دون التظاهر بأن التغيير نهائي. احتفظ بالقيمة المؤكدة الأخيرة وأضف إشارة انتظار واضحة.
على سبيل المثال، في شاشة CRM حيث تضغط "وُسِم كمُدفوع"، قد يرفض الخادم ذلك (صلاحيات، تحقق، مسترد مسبقًا). بدلًا من إعادة كتابة كل الأرقام المشتقة فورًا، حدّث الحالة بعلامة "Saving..." طفيفة، احتفظ بالمجاميع دون تغيير، ولا حدّث المجاميع إلا بعد التأكيد.
الأنماط الجيدة بسيطة ومتسقة: شارة صغيرة "Saving..." قرب العنصر المتغير، تعطيل الإجراء مؤقتًا (أو تحويله إلى تراجع) حتى يستقر الطلب، أو تمييز القيمة التفاؤلية بصريًا كمؤقتة (نص أخف أو مؤشر دوار صغير).
إذا كان استجابة الخادم يمكن أن تؤثر على أماكن كثيرة (مجاميع، فرز، حقول محسوبة، صلاحيات)، فإن إعادة الجلب عادةً أكثر أمانًا من محاولة تعديل كل شيء. إذا كان تغييرًا صغيرًا ومعزولًا (إعادة تسمية ملاحظة، تبديل علامة)، فالتعديل المحلي غالبًا ما يكون كافيًا.
قاعدة مفيدة: عدّل الشيء الذي غيّره المستخدم فقط، ثم أعد جلب أي بيانات مشتقة أو مجمّعة أو مشتركة عبر الشاشات.
تعمل الواجهة التفاؤلية عندما يحافظ نموذج بياناتك على تتبّع ما هو مؤكد مقابل ما هو تخمين. إذا مثلت هذه الفجوة صراحةً، فإن لحظات "لماذا عاد هذا؟" تصبح نادرة.
للعناصر المنشأة حديثًا، قم بتعيين معرف مؤقت من العميل (مثل temp_12345 أو UUID)، ثم استبدله بالمعرف الحقيقي من الخادم عند وصول الاستجابة. هذا يسمح للقوائم، والاختيار، وحالة التحرير بأن تتطابق بسلاسة.
مثال: يضيف المستخدم مهمة. تقوم بعرضها فورًا مع id: "temp_a1". عندما يرد الخادم بمعرف id: 981، تستبدل المعرف في مكان واحد، وتستمر أي مفاتيح مرتكزة على المعرف بالعمل.
علم تحميل بمستوى الشاشة واحد فظ جدًا. تتبّع الحالة على مستوى العنصر (أو حتى الحقل) الذي يتغير. بهذه الطريقة يمكنك إظهار واجهة انتظار دقيقة، إعادة المحاولة فقط لما فشل، وتجنب حظر إجراءات غير ذات صلة.
شكل عملي للعنصر:
id: حقيقي أو مؤقتstatus: pending | confirmed | failedoptimisticPatch: ما غيرته محليًا (صغير ومحدد)serverValue: آخر بيانات مؤكدة (أو طابع confirmedAt)rollbackSnapshot: القيمة المؤكدة السابقة التي يمكنك استعادتهاالتحديثات التفاؤلية أكثر أمانًا عندما تلمس فقط ما غيّره المستخدم فعليًا (مثل تبديل completed) بدلًا من استبدال الكائن بأكمله بنسخة متوقعة جديدة. استبدال الكائن بأكمله يسهل محو تعديلات أحدث، أو حقول أضافها الخادم، أو تغييرات متزامنة.
تجربة تحديث تفاؤلية جيدة تبدو فورية، لكنها في النهاية تتطابق مع ما يقوله الخادم. عامل التغيير التفاؤلي كشيء مؤقت، واحفظ ما يكفي من بيانات التعقب لتأكيده أو التراجع عنه بأمان.
مثال: يحرّر المستخدم عنوان مهمة في قائمة. تريد أن يتحدّث العنوان فورًا، لكنك تحتاج أيضًا للتعامل مع أخطاء التحقق وتنسيق الخادم.
طبّق التغيير التفاؤلي فورًا في الحالة المحلية. خزّن تصحيحًا صغيرًا (أو لقطة) حتى يمكنك التراجع.
أرسل الطلب مع معرف طلب (رقم متزايد أو معرف عشوائي). هذا كيف تطابق الاستجابات مع الإجراء الذي أثاره.
ضع العنصر في حالة انتظار. لا يجب أن يوقف الانتظار واجهة المستخدم. يمكن أن يكون مؤشر دوار صغير، نص باهت، أو "Saving...". المفتاح أن يفهم المستخدم أنه لم يؤكد بعد.
عند النجاح، استبدل البيانات العميلية المؤقتة بنسخة الخادم. إذا عدّل الخادم أي شيء (أزال مسافات، غير الحالة، حدّث علامات زمنية)، حدّث الحالة المحلية لتطابق.
عند الفشل، تراجع فقط عما غيّره هذا الطلب وأظهر خطأ محليًا واضحًا. تجنب التراجع عن أجزاء غير ذات صلة من الشاشة.
إليك شكل صغير يمكنك اتباعه (غير تابع لمكتبة):
const requestId = crypto.randomUUID();
applyOptimistic({ id, title: nextTitle, pending: requestId });
try {
const serverItem = await api.updateTask({ id, title: nextTitle, requestId });
confirmSuccess({ id, requestId, serverItem });
} catch (err) {
rollback({ id, requestId });
showError("Could not save. Your change was undone.");
}
تفصيلان يمنعان كثيرًا من الأخطاء: خزّن معرف الطلب على العنصر أثناء الانتظار، ولا تؤكد أو تتراجع إلا إذا طابقت المعرفات. هذا يمنع استجابات أقدم من الكتابة فوق تعديلات أحدث.
تتفكك الواجهة التفاؤلية عندما تجيب الشبكة بترتيب خاطئ. فشل كلاسيكي: المستخدم يحرر عنوانًا، يحرّره مرة أخرى على الفور، ويصل الطلب الأول آخرًا. إذا طبّقت تلك الاستجابة المتأخرة، تقفز الواجهة إلى قيمة أقدم.
الحل هو معاملة كل استجابة كـ "قد تكون ذات صلة" وطبّقها فقط إذا طابقت نية المستخدم الأحدث.
نمط عملي هو معرف طلب في العميل (عداد) مُرفق بكل تغيير تفاؤلي. خزّن أحدث معرف لكل سجل. عندما تصل الاستجابة، قارن المعرفات. إذا كانت الاستجابة أقدم من الأحدث، فتجاهلها.
فحوصات النسخة أيضًا مفيدة. إذا أعاد الخادم updatedAt أو version أو etag، اقبل فقط الاستجابات الأحدث مما تعرضه الواجهة بالفعل.
خيارات أخرى يمكنك دمجها:
مثال (حماية معرف الطلب):
let nextId = 1;
const latestByItem = new Map();
async function saveTitle(itemId, title) {
const requestId = nextId++;
latestByItem.set(itemId, requestId);
// optimistic update
setItems(prev => prev.map(i => i.id === itemId ? { ...i, title } : i));
const res = await api.updateItem(itemId, { title, requestId });
// ignore stale response
if (latestByItem.get(itemId) !== requestId) return;
// reconcile with server truth
setItems(prev => prev.map(i => i.id === itemId ? { ...i, ...res.item } : i));
}
إذا كان المستخدمون يكتبون بسرعة (ملاحظات، عناوين، بحث)، فكّر في إلغاء أو تأخير الحفظ حتى يتوقفوا عن الكتابة. هذا يقلل حمل الخادم ويخفض احتمالية وصول استجابات متأخرة وتسبّبها في وميض على الواجهة.
الفشل هو المكان الذي قد تفقد فيه الواجهة التفاؤلية الثقة. أسوأ تجربة هي تراجع مفاجئ بلا شرح.
الافتراضي الجيد للتعديلات: احتفظ بقيمة المستخدم على الشاشة، وسمّها كغير محفوظة، وأظهر خطأ مضمنًا بالضبط حيث حرّرها. إذا سما المستخدم مشروعًا من "Alpha" إلى "Q1 Launch"، لا تعيده إلى "Alpha" ما لم تضطر. احتفظ بـ"Q1 Launch"، أضف "غير محفوظ. الاسم مستخدم"، ودعهم يصلحون ذلك.
التغذية الراجعة المضمنة تبقى مرتبطة بالحقل أو الصف الذي فشل. تتجنب لحظة "ماذا حدث للتو؟" حيث يظهر إشعار عام بينما الواجهة تغيرت بصمت.
إشارات موثوقة تتضمن "Saving..." أثناء الإرسال، "Not saved" عند الفشل، تمييز خفيف على الصف المتأثر، ورسالة قصيرة تخبر المستخدم بما يفعل بعد ذلك.
إعادة المحاولة مفيدة تقريبًا دائمًا. التراجع مناسب للأفعال السريعة التي قد يندم عليها الشخص (مثل الأرشفة)، لكنه قد يُربك بالنسبة للتعديلات التي يريد المستخدم فيها القيمة الجديدة بوضوح.
عندما تفشل الطفرة:
إذا اضطررت للتراجع (مثلاً تغيرت الصلاحيات ولم يعد بالإمكان التحرير)، ففسّر السبب واستعد لحقيقة الخادم: "لم يتم الحفظ. لم يعد لديك صلاحية التحرير."
عامل استجابة الخادم كإيصال، وليس مجرد علم نجاح. بعد اكتمال الطلب، صلّح: احتفظ بما قصد المستخدم، واقبل ما يعرفه الخادم أفضل.
إعادة الجلب الكاملة هي الأكثر أمانًا عندما قد يكون الخادم قد غيّر أكثر مما خمّنت محليًا. كما أنها أسهل للفهم.
إعادة الجلب عادةً خيار أفضل عندما تؤثر الطفرة على سجلات عديدة (نقل عناصر بين قوائم)، عندما يمكن أن تغير الصلاحيات أو قواعد سير العمل النتيجة، عندما يعيد الخادم بيانات جزئية، أو عندما تحدّث عملاء آخرون نفس العرض كثيرًا.
إذا عاد الخادم بالكيان المحدث (أو حقول كافية)، قد يكون الدمج تجربة أفضل: تبقى الواجهة مستقرة بينما تقبل حقيقة الخادم.
غالبًا ما يأتي الانحراف من استبدال حقول مملوكة للخادم بكائن تفاؤلي. فكّر في العدادات، والقيم المحسوبة، والطوابع الزمنية.
مثال: تضبط تفاؤليًا likedByMe=true وتزيد likeCount. قد يقوم الخادم بإلغاء التكرار والعودة بعدد مختلف، بالإضافة إلى updatedAt محدث.
نهج دمج بسيط:
عندما يكون هناك تعارض، قرر مسبقًا. "آخر كتابة تفوز" مناسب للتبديلات. الدمج على مستوى الحقل أفضل للنماذج.
تتبّع علامة "تسخّخ منذ الطلب" لكل حقل أو رقم نسخة محلية يسمح لك بتجاهل قيم الخادم للحقول التي غيّرها المستخدم بعد بدء الطفرة، مع قبول بقية حقائق الخادم.
إذا رفض الخادم الطفرة، فضّل رسائل خفيفة ومحددة بدل تراجع مفاجئ. احتفظ بإدخال المستخدم، ظلل الحقل، وعرّض الرسالة. احفظ التراجعات للحالات التي لا يمكن فيها قبول الإجراء فعليًا (مثال: حذفت عنصرًا تفاؤليًا ورفض الخادم الحذف).
القوائم هي المكان الذي تبدو فيه الواجهة التفاؤلية رائعة وتنهار بسهولة. عنصر واحد يتغير قد يؤثر على الترتيب، والمجاميع، والفلاتر، وصفحات متعددة.
للانشاءات، اعرض العنصر الجديد فورًا لكن وسمه كمعلق مع معرف مؤقت. حافظ على موضعه ثابتًا حتى لا يقفز.
للحذف، نمط آمن هو إخفاء العنصر فورًا لكن الاحتفاظ بـ"سجل شبح" قصير الأمد في الذاكرة حتى يؤكد الخادم. هذا يدعم التراجع ويجعل الفشل أسهل للتعامل.
إعادة الترتيب صعبة لأنها تمس عدة عناصر. إن قمت بإعادة الترتيب تفاؤليًا، خزّن الترتيب السابق حتى يمكنك استعادته عند الحاجة.
مع الترقيم أو التمرير اللانهائي، قرر أين تنتمي الإدخالات التفاؤلية. في الخلاصات، عادةً تذهب العناصر الجديدة للأعلى. في الكتالوجات المرتبة بواسطة الخادم، الإدراج المحلي قد يضلل لأن الخادم قد يضع العنصر في مكان آخر. حل وسط عملي: أدرج في القائمة المرئية مع شارة انتظار، وكن مستعدًا لنقلها بعد استجابة الخادم إذا اختلف مفتاح الفرز النهائي.
عندما يصبح المعرف المؤقت معرفًا حقيقيًا، قم بإزالة التكرار عبر مفتاح مستقر. إن طابقت فقط بالمعرف، يمكنك عرض نفس العنصر مرتين (المؤقت والموكد). احتفظ بخريطة tempId-to-realId واستبدل في المكان حتى لا يعاد تعيين موضع التمرير والاختيار.
الأعداد والفلاتر أيضًا حالة قائمة. حدّث الأعداد تفاؤليًا فقط عندما تكون واثقًا من موافقة الخادم. وإلا، اعلنه متجددًا ووافق بعد الاستجابة.
معظم أخطاء التحديث التفاؤلي ليست متعلقة بReact حقًا. تأتي من معاملة التغيير التفاؤلي كـ "الحقيقة الجديدة" بدلًا من تخمين مؤقت.
تحديث كائن كامل أو شاشة بأكملها تفاؤليًا عندما تغيّر حقل واحد يوسّع نطاق التأثير. التصحيحات اللاحقة من الخادم قد تمحو تعديلات غير ذات صلة.
مثال: نموذج ملف شخصي يستبدل كائن user كامل عند تبديل إعداد. بينما الطلب قيد التنفيذ، يحرّر المستخدم اسمه. عند وصول الاستجابة، استبدالك قد يعيد الاسم القديم.
اجعل التصحيحات التفاؤلية صغيرة ومركّزة.
مصدر انحراف آخر هو نسيان مسح علامات الانتظار بعد النجاح أو الخطأ. تبقى الواجهة نصف محملة، وقد تتعامل منطق لاحق معها كأنها لا تزال تفاؤلية.
إذا تتبّعّت حالة الانتظار لكل عنصر، امسحها باستخدام نفس المفتاح الذي استخدمته لضبطها. المعرفات المؤقتة غالبًا ما تسبب "عناصر شبحية قيد الانتظار" عندما لا تُربط المعرف الحقيقي في كل الأماكن.
أخطاء التراجع تحدث عندما تُخزن اللقطة متأخرًا أو على نطاق واسع جدًا.
إذا قام المستخدم بتعديلين سريعين، قد تنتهي إلى التراجع عن التعديل #2 باستخدام لقطة ما قبل التعديل #1. تقفز الواجهة إلى حالة لم يرها المستخدم أبدًا.
الإصلاح: التقط اللقطة بالمنتصف الدقيق الذي ستستعيده، ونوّعها لتتعلق بمحاولة طفرة محددة (عادة باستخدام معرف الطلب).
عمليات الحفظ الحقيقية غالبًا ما تكون متعددة الخطوات. إذا فشل الخطوة 2 (مثل تحميل صورة)، فلا تَسِر بصمت خطوة 1. أرِ ما حفظ، وما لم يُحفَظ، وما يمكن للمستخدم فعله بعد ذلك.
أيضًا، لا تفترض أن الخادم سيرجع لك تمامًا ما أرسلته. الخوادم تنظف النص، وتطبّق الصلاحيات، وتضع الطوابع الزمنية، وتعيّن المعرفات، وتحذف الحقول. دائمًا صلّح من الاستجابة (أو أعد الجلب) بدل الوثوق بالتصحيح التفاؤلي إلى الأبد.
تعمل الواجهة التفاؤلية عندما تكون متوقعة. عامل كل تغيير تفاؤلي كمعاملة صغيرة: له معرف، حالة انتظار مرئية، تبديل نجاح واضح، ومسار فشل لا يفاجئ الناس.
قائمة مراجعة قبل الإصدار:
إذا كنت تنسّق بسرعة، اجعل النسخة الأولى صغيرة: شاشة واحدة، طفرة واحدة، تحديث واحد للقائمة. أدوات مثل Koder.ai (koder.ai) يمكن أن تساعدك على رسم واجهة المستخدم وواجهة برمجة التطبيقات أسرع، لكن القاعدة نفسها تنطبق: مدل حالة الانتظار مقابل المؤكد حتى لا يفقد العميل تتبّع ما قبله الخادم بالفعل.
تحدّث واجهة المستخدم التفاؤلية الشاشة فورًا قبل تأكيد الخادم للتغيير. تعطي هذه التقنية إحساسًا بالسرعة، لكن يجب مطابقة الحالة مع استجابة الخادم حتى لا ينحرف العرض عن الحالة المحفوظة الحقيقية.
يحدث انحراف البيانات عندما يحتفظ الواجهة بتخمين تفاؤلي كما لو كان مؤكدًا، بينما يحفظ الخادم شيئًا مختلفًا أو يرفض التغيير. يظهر ذلك عادة بعد تحديث الصفحة أو في نافذة أخرى، أو عندما تصل الاستجابات بترتيب مختلف بسبب شبكات بطيئة.
تجنب أو كُن حذرًا مع التحديثات التفاؤلية في المسائل المالية والفوترة، والإجراءات التي لا رجعة فيها، وتغييرات الأدوار والصلاحيات، وسير العمل الذي يخضع لقواعد خادم معقدة. في هذه الحالات، من الأفضل إظهار حالة انتظار واضحة والانتظار للتأكيد قبل تغيير ما يؤثر على المجاميع أو الوصول.
اعتبر الخلفية مصدر الحقيقة لأي شيء له معنى تجاري خارج الشاشة الحالية، مثل الأسعار والصلاحيات والحقول المحسوبة والعدادات المشتركة. احتفظ بحالة واجهة المستخدم المحلية للمسودات، والتركيز، و«قيد التحرير»، والفلاتر العرضية، والحالة العرضية فقط.
أرِ قيمة "قيد الانتظار" صغيرة ومتسقة بالقرب من المكان الذي حدث فيه التغيير، مثل "Saving..." أو نص باهت أو مؤشر دوار بسيط. الهدف أن يفهم المستخدم أنها مؤقتة دون إغلاق الصفحة بأكملها.
استخدم معرف عميل مؤقت (مثل UUID أو temp_...) عند إنشاء العنصر، ثم استبدله بمعرف الخادم الحقيقي عند النجاح. هذا يحافظ على مفاتيح القوائم، والاختيار، وحالة التحرير ثابته حتى لا يحدث وميض أو تكرار.
لا تستخدم علم تحميل عام للشاشة؛ تتبّع حالة الانتظار لكل عنصر (أو لكل حقل) حتى يظهر التغيير فقط عند الشيء المتأثر. خزن تصحيحًا تفاؤليًا صغيرًا ولقطة استرجاع حتى تستطيع تأكيد أو التراجع عن ذلك التغيير فقط دون التأثير على الواجهة غير ذات الصلة.
أرفق معرف طلب بكل تغير وخزن أحدث معرف لكل سجل. عندما تصل الاستجابة، طبّقها فقط إذا طابقت أحدث معرف؛ وإلا تجاهلها حتى لا تعود الردود القديمة لتعيد واجهة المستخدم إلى قيمة أقدم.
لأغلب التعديلات، احتفظ بقيمة المستخدم ظاهرة، وصفها كغير محفوظة، وأظهر خطأ مضمنًا مكان التحرير مع خيار إعادة المحاولة. قم بالاسترجاع الصارم فقط عندما لا يمكن قبول التغيير فعليًا (مثال: فقدان الصلاحية) ووضح السبب.
أعد الجلب عندما يؤثر التغيير على أماكن متعددة مثل المجاميع، والفرز، والصلاحيات أو الحقول المشتقة لأن تصحيح كل شيء محليًا سهل الخطأ. ادمج محليًا عندما يكون التحديث معزولًا ولديك الكيان المحدث من الخادم، ثم امسح حالة الانتظار واقبل الحقول التي يملكها الخادم مثل الطوابع الزمنية والقيم المحسوبة.