اكتشف كيف يوفّق Fiber بين الشجرة، ويجمّع التحديثات، ويجعل عمل العرض قابلاً للمقاطعة.
افتح هذا الدرس في Kodokonمنذ React 16، صار المحرّك الداخلي يُدعى Fiber: كل عنصر في شجرتك يُمثَّل بعقدة fiber، وهي وحدة عمل. في الواقع، يحتفظ React بـ شجرتين (تقنية التخزين المؤقّت المزدوج): الشجرة current التي تعكس ما هو معروض على الشاشة، والشجرة workInProgress التي تُبنى أثناء العرض. يجري العمل في مرحلتين متمايزتين: مرحلة العرض (استدعاء مكوّناتك، حساب الفروقات)، وهي قابلة للمقاطعة والإعادة، ثم مرحلة الإيداع (تطبيق التعديلات على الـ DOM، تشغيل التأثيرات)، وهي دائماً متزامنة وغير قابلة للمقاطعة. يعتمد التوفيق على استدلالين يخفّضان تكلفة المقارنة إلى تكلفة خطّية: إذا تغيّر نوع عنصر في موضع معيّن، يدمّر React الشجرة الفرعية بأكملها ويعيد بناءها، بما في ذلك الحالة؛ وبالنسبة إلى القوائم، تتيح خاصية key لـ React مطابقة العناصر بين عرضين.
function List({ items }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>
<input defaultValue={item.label} />
</li>
))}
</ul>
);
}
// With key={index}, deleting the first item
// would shift the content of the remaining inputs.الآلية الأساسية الثانية: التجميع. حين تُطلَق عدّة تحديثات للحالة ضمن السياق نفسه، يجمعها React في عرض واحد. قبل React 18، كان هذا التجميع يعمل فقط داخل معالِجات أحداث React؛ ومنذ createRoot، صار تلقائياً في كل مكان: المؤقّتات، الوعود، المستمعون الأصليون. ولهذا أيضاً تُرجِع قراءة الحالة مباشرةً بعد setState القيمة القديمة: فالتحديث مُجدوَل، لا مُطبَّق.
function handleTick() {
setCount((c) => c + 1);
setFlag((f) => !f);
// React 18: a single render, even here.
}
setTimeout(handleTick, 100);
// Before React 18, this timer would trigger
// two successive renders.العرض المتزامن يدفع المنطق أبعد: يمكن مقاطعة مرحلة العرض أو استئنافها أو حتى التخلّي عنها وإعادة تشغيلها إذا ورد تحديث أكثر إلحاحاً. يرتّب React التحديثات حسب الأولوية (الـ lanes): ضغطة مفتاح ملحّة، أمّا تصفية قائمة موسومة بـ startTransition فليست كذلك. النتيجة المباشرة: يجب أن تكون دوال مكوّناتك نقيّة، لأن React قد يستدعيها عدّة مرّات من دون أن يُودِع النتيجة أبداً. حالة حدّية أخرى: مخزن خارجي (Redux، Zustand، مفردة محلية الصنع) قُرِئ أثناء عرض قابل للمقاطعة يمكن أن يتغيّر في منتصف العرض، منتِجاً واجهة غير متّسقة تُعرَف بالـ tearing (التمزّق). واجهة useSyncExternalStore تحلّ هذه المشكلة بإجبار عرض متزامن عند تغيّر المخزن.
import { useSyncExternalStore } from 'react';
function useWindowWidth() {
return useSyncExternalStore(
(notify) => {
window.addEventListener('resize', notify);
return () =>
window.removeEventListener('resize', notify);
},
() => window.innerWidth
);
}<div> عنصراً <span> في الموضع نفسه؟createRoot، كم عدد عمليات العرض التي يُطلقها استدعاءان لـ setState داخل setTimeout؟