يعطيك useDeferredValue نسخة من قيمة يُسمح لها بالتأخر. عندما تتغير القيمة، تعيد React رسم الشاشة أولًا بالنسخة القديمة، ما يُبقي الكتابة فورية، ثم تعرض الجزء البطيء بالقيمة الجديدة في الخلفية.
اكتب كلمة بسرعة. يواكبك الحقل وتبهت القائمة ما دامت متأخرة. تسجّل Console سطر SlowList rendering واحدًا لكل حرف، لأن SlowList يسجّل بمجرد أن تبدأ React عرضه، لكن قوائم القيم الوسيطة لا تصل إلى الشاشة أبدًا: قاطع كل حرف جديد العرض الخلفي، وبدأت React من جديد بأحدث نص. غيّر query={deferredQuery} إلى query={query} واكتب مرة أخرى: تنتظر الآن كل ضغطة مفتاح ربع ثانية للقائمة.
الصيغة
const deferredValue = useDeferredValue(value);
const deferredValue = useDeferredValue(value, initialValue); // React 19
valueأي شيء تعرضه: نص من حقل، أو رقم، أو كائن. تعمل القيم البسيطة أفضل، لأن الكائن الجديد في كل عرض يُحسب دائمًا تغييرًا.- القيمة المُرجعة تساوي
valueمعظم الوقت. وبعد أن تتغيرvalueمباشرة، تظل تحمل القيمة السابقة لعرض واحد، ثم تلحق في عرض خلفي.
كيف يعمل
كل تغيير ينتج عرضين:
- عرض عاجل بالقيمة
valueالجديدة وdeferredValueالقديمة. يُظهر الحقل النص الجديد. والمكوّنات التي لا تستقبل إلاdeferredValueتحصل على props نفسها كما من قبل. - عرض خلفي بالقيمة
deferredValueالجديدة. هذا هو البطيء. تعرضه React على شرائح وتتخلى عنه إذا تغيرتvalueمن جديد قبل أن ينتهي، ثم تبدأ من جديد بأحدث قيمة.
يمكنك مراقبة ذلك بتسجيل القيمتين:
اكتب حرفًا واحدًا، a مثلًا، فتطبع Console سطرين إضافيين: أولًا text = "a" مع deferred = ""، ثم كلاهما "a". ذلك العرض الأول هو ما يُبقي الكتابة سريعة، لكن فقط إذا استطاع الجزء البطيء تخطيه. ولهذا يكون SlowList في المثال الأول ملفوفًا بـ memo: أثناء العرض العاجل لم تتغير الـ prop المسماة query، فتتخطاه React. من دون memo سيظل العرض العاجل يشغّل القائمة البطيئة كلها، فقط بالنص القديم، ولن تكسب شيئًا.
عرض محتوى قديم
أثناء تقدم العرض الخلفي تختلف value والقيمة المؤجلة. قارن بينهما لتخبر المستخدم بأن المحتوى قديم، دون إخفائه:
const isStale = query !== deferredQuery;
<div style={{ opacity: isStale ? 0.5 : 1, transition: 'opacity 0.2s' }}>
<SlowList query={deferredQuery} />
</div>
هذا عادة أجمل من مؤشر دوار: تبقى النتائج القديمة قابلة للقراءة وتتغير في مكانها عندما تجهز الجديدة. وتعمل الفكرة نفسها مع Suspense: إذا جعلت القيمة المؤجلة مكوّنًا يُعلَّق أثناء تحميل البيانات، تظل React تعرض المحتوى القديم بدلًا من البديل حتى يجهز المحتوى الجديد (راجع Suspense وlazy).
القيمة الابتدائية (React 19)
افتراضيًا لا يوجد في العرض الأول ما تتأخر عنه، فتكون القيمة المؤجلة هي القيمة الحقيقية من البداية. ومنذ React 19 يمكنك تمرير وسيط ثانٍ. في العرض الأول يُرجع الخطاف initialValue، ثم تعيد React العرض في الخلفية بالقيمة الحقيقية. وهذا يتيح لك عرض شيء رخيص فورًا وملء الجزء المكلف بعد ذلك مباشرة.
تُظهر Console عرضين: الأول بـ ""، ثم بالنص الكامل. احذف الوسيط الثاني فلا يُسجَّل إلا عرض واحد، بالنص الكامل مباشرة.
useDeferredValue مقابل useTransition
كلاهما يعلّم العمل كغير عاجل ويستخدم آلية العرض نفسها. الفرق هو أين تضعهما:
- useTransition يغلّف التحديث. تستدعي
startTransition(() => setQuery(text))، فتحتاج إلى دالة الضبط. وتحصل علىisPendingمجانًا. - useDeferredValue يغلّف القيمة. تستدعيه حيث تُستخدم القيمة، فيعمل عندما تصل القيمة كـ prop أو تأتي من خطاف لا تتحكم فيه. وتحسب «الانتظار» بنفسك بـ
value !== deferredValue.
// You own the state: either works, a transition is direct
startTransition(() => setQuery(nextQuery));
// You only receive the value
function SearchResults({ query }) {
const deferredQuery = useDeferredValue(query);
return <SlowList query={deferredQuery} />;
}
useDeferredValue مقابل التأخير والتقييد
التأخير (debounce) ينتظر مدة ثابتة بعد آخر تغيير؛ والتقييد (throttle) يحدّث مرة واحدة على الأكثر في كل فترة. كلاهما يختار مهلة مسبقًا، فيجعلان الصفحة على حاسوب محمول سريع أبطأ مما يلزم، وقد تكون المهلة على هاتف بطيء ما زالت قصيرة جدًا.
ليس لـ useDeferredValue أي مهلة. يبدأ العرض الخلفي فورًا، فتتحدث القائمة على جهاز سريع على الفور تقريبًا، وعلى جهاز بطيء تُبقي React الحقل سريع الاستجابة وتعرض القائمة بقدر ما تستطيع. والعمل الخلفي قابل للمقاطعة أيضًا، وهو ما لا يكونه العرض المؤخَّر: بمجرد أن يبدأ عرض مؤخَّر، يحجب حتى ينتهي.
وما لا يفعله هو تقليل طلبات الشبكة. إذا بدأت كل قيمة جديدة طلبًا، فما زالت القيمة المؤجلة تتغير مع كل عرض يكتمل، لذا أخّر الطلب نفسه عندما يهم عدد الطلبات.
أخطاء شائعة
- نسيان
memo. كما يظهر أعلاه، يجب أن يتمكن المكوّن البطيء من تخطي العرض العاجل. إذا لم يكن ملفوفًا بـmemo، أو كانت إحدى props الأخرى كائنًا جديدًا في كل عرض، فإنه يُعرض مرتين لكل ضغطة مفتاح بدلًا من مرة. - تمرير كائن جديد. ينشئ
useDeferredValue({ query })كائنًا جديدًا في كل عرض، فتبدو القيمة المؤجلة متغيرة دائمًا وتبدأ React عرضًا خلفيًا إضافيًا في كل مرة. أجّل القيمة البسيطة (useDeferredValue(query)) وابنِ الكائنات منها. - توقع طلبات أقل. المكوّن الذي يجلب البيانات بناءً على القيمة المؤجلة ما زال يجلب لكل قيمة ينتهي عرضها.
متى تلجأ إليه
استخدم useDeferredValue عندما يقود حقل واحد جزءًا من الصفحة بطيئًا في العرض، ويمكن حفظ الجزء البطيء، ولا مانع لديك من عرض نتائج أقدم قليلًا للحظة: نتائج البحث، أو رسم بياني يُعاد رسمه من مرشح، أو معاينة لنص منسق. وإذا كان الجزء البطيء بطيئًا لسبب يمكنك إصلاحه، مثل عرض 5,000 صف بينما يظهر 50، فأصلح ذلك أولًا. ولنسخة هذا النمط التي تملك فيها دالة الضبط، راجع useTransition.
الأسئلة الشائعة
ماذا يفعل useDeferredValue؟
يُرجع نسخة من قيمة قد تتأخر عن الحقيقية. عندما تتغير القيمة، تعرض React أولًا بالقيمة المؤجلة القديمة، ثم تعيد العرض في الخلفية بالجديدة، ويمكن مقاطعة ذلك العرض الخلفي.
لماذا لا يجعل useDeferredValue قائمتي أسرع؟
يجب أن يكون المكوّن البطيء ملفوفًا بـ memo وأن يستقبل القيمة المؤجلة. وإلا فإن العرض العاجل ما زال يعرض المكوّن البطيء، فقط بالقيمة القديمة، وتبقى الكتابة بطيئة.
ما الفرق بين useDeferredValue وuseTransition؟
يغلّف useTransition تحديث الحالة، فتحتاج إلى الوصول إلى دالة الضبط. أما useDeferredValue فيغلّف قيمة، فيعمل عندما لا تستقبل إلا القيمة، مثلًا كـ prop أو من خطاف لا تتحكم فيه.
هل useDeferredValue تأخير (debounce)؟
لا. لا توجد مهلة ثابتة. تبدأ React العرض المؤجل مباشرة بعد العاجل، وعلى جهاز سريع يكاد التأخر لا يُرى. ولا يقلل أيضًا طلبات الشبكة بنفسه.
ما هو الوسيط الثاني في useDeferredValue؟
منذ React 19 يمكنك تمرير initialValue. في العرض الأول يُرجعها الخطاف، ثم تعيد React العرض في الخلفية بالقيمة الحقيقية.