في جملة واحدة
React هي مكتبة JavaScript لبناء واجهات المستخدم عن طريق تقسيمها إلى أجزاء كود مستقلة وقابلة لإعادة الاستخدام تسمى "مكونات" (components)، والتي تتحدث تلقائيًا عندما تتغير بياناتها.
المشكلة التي تحلها
تخيل معي تطوير الويب في الأيام الخوالي (ولنقل في أوائل 2010). كان لديك صفحة ويب، وأردت أن تجعلها تفاعلية. ربما أردت زر "إعجاب" يتحول إلى اللون الأزرق ويحدّث عدادًا عند النقر عليه. باستخدام أداة مثل jQuery، كنت ستكتب كودًا يقول: "ابحث عن الزر في الصفحة، استمع لنقرة، وعندما تحدث، ابحث عن عنصر العداد وغيّر نصه، ثم ابحث عن أيقونة الزر وغيّر لونها".
هذا نهج أمرِي (imperative). أنت تعطي المتصفح وصفة خطوة بخطوة لكيفية تغيير الصفحة. هذا يعمل مع زر إعجاب. ولكن ماذا عن صفحة أخبار في وسيلة تواصل اجتماعي مليئة بمئات المنشورات والتعليقات والإشعارات ونافذة دردشة حية، وكلها تتحدث في نفس الوقت؟ ستتحول هذه "الوصفات" إلى فوضى عارمة من "كود السباغيتي" المتشابك. أي تغيير في مكان ما يمكن أن يكسر شيئًا آخر بشكل غير متوقع. كان الحفاظ على تناسق واجهة المستخدم (UI) مع بيانات التطبيق معركة مستمرة ومليئة بالأخطاء.
كانت فيسبوك تعيش هذا الكابوس على نطاق واسع. في عام 2011، أنشأوا React لحل هذه المشكلة داخليًا. قدمت React نموذجًا تصريحيًا (declarative). بدلاً من إخبار المتصفح كيف يغير الأشياء، أنت فقط تصف ماذا يجب أن تبدو عليه واجهة المستخدم لأي حالة (state) معينة. أنت تصرح: "إذا كان المنشور liked، يجب أن يكون الزر أزرق والعدد 101. إذا لم يكن liked، يكون الزر رماديًا والعدد 100". هذا كل شيء. عندما تتغير حالة liked من false إلى true، تتولى React مهمة اكتشاف الطريقة الأكثر كفاءة لتحديث الزر والعداد. هي تتولى الـ "كيف"، حتى تتمكن أنت من التركيز على الـ "ماذا".
كان هذا التحول من التفكير الأمري إلى التصريحي بمثابة ثورة. لقد جعل بناء واجهات المستخدم المعقدة والقائمة على البيانات أكثر قابلية للتنبؤ والتوسع وأسهل في تصحيح الأخطاء.
كيف تعمل تحت الغطاء
"سحر" React ليس سحرًا حقًا؛ إنه مزيج ذكي من ثلاث أفكار أساسية: JSX، والمكونات مع الحالة (Components with State)، و الـ DOM الافتراضي (Virtual DOM).
JSX: سكر صياغي لتجميل JavaScript
عندما تنظر إلى كود React، ترى شيئًا يشبه إلى حد كبير HTML مدمجًا مباشرة في JavaScript. هذا هو JSX (اختصار لـ JavaScript XML).
const greeting = <h1>Hello, FlowingDev user!</h1>;
متصفحك ليس لديه أي فكرة عما يجب فعله بهذا. إنه ليس كود JavaScript صالحًا. قبل أن يتمكن هذا الكود من العمل، يجب أن يتم "تحويله" (transpiled) بواسطة أداة مثل Babel. السطر أعلاه المكتوب بـ JSX يتحول إلى استدعاء دالة JavaScript عادية.
const greeting = React.createElement("h1", null, "Hello, FlowingDev user!");
كل وسم JSX يصبح استدعاء للدالة React.createElement(). استدعاء هذه الدالة لا ينشئ عنصر HTML حقيقيًا. إنه ينشئ كائن JavaScript خفيف الوزن يصف العنصر الذي تريد إنشاءه—نوعه (h1)، خصائصه (لا شيء في هذه الحالة، لذا null)، وأبناؤه ("Hello..."). هذه الكائنات هي اللبنات الأساسية للـ DOM الافتراضي.
لذا، JSX هو مجرد "سكر صياغي". يسمح لنا بوصف واجهة المستخدم الخاصة بنا بصيغة مألوفة تشبه HTML، ولكن تحت الغطاء، نحن فقط ننشئ شجرة من كائنات JavaScript.
المكونات والحالة (Components and State): العقل المدبر للعملية
المكون (Component) هو دالة JavaScript تعيد بعضًا من JSX. إنه قطعة من واجهة المستخدم قابلة لإعادة الاستخدام ومستقلة بذاتها.
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
مكون Greeting هذا بسيط. يأخذ name كمدخل (يسمى "prop") ويعرض عنوانًا. ولكن ماذا لو أردنا مكونًا يتذكر الأشياء ويتغير بمرور الوقت، مثل عداد؟ لذلك، نحتاج إلى "الحالة" (state).
الحالة هي بيانات "يمتلكها" المكون ويمكنه تغييرها. توفر React دوال خاصة تسمى "hooks" لإدارة هذا. وأكثرها شيوعًا هو useState.
import { useState } from 'react';
function Counter() {
// "count" هو متغير الحالة الخاص بنا.
// "setCount" هي الدالة *الوحيدة* التي يمكننا استخدامها لتحديثه.
const [count, setCount] = useState(0); // القيمة الأولية هي 0.
return (
<div>
<p>لقد نقرت {count} مرات</p>
<button onClick={() => setCount(count + 1)}>
انقر هنا
</button>
</div>
);
}
عندما تستدعي setCount(count + 1)، أنت لا تغير متغيرًا فحسب. أنت تخبر React: "مرحبًا، حالة مكون Counter هذا قد تغيرت. يرجى إعادة تشغيل هذه الدالة للحصول على واجهة المستخدم الجديدة، ثم تحديث الشاشة". تقوم React بعد ذلك بـ "إعادة تصيير" (re-render) المكون، وتحصل على JSX الجديد مع count المحدث، وتقوم بتحديث صفحة الويب بكفاءة.
الـ DOM الافتراضي (Virtual DOM): سلاح React السري
تغيير الـ DOM الحقيقي للمتصفح (الشجرة الحية لعناصر HTML) عملية بطيئة. الأمر أشبه بإعادة تزيين غرفة عن طريق هدم جميع الجدران وإعادة بنائها، حتى لو كنت تريد فقط تحريك إطار صورة.
تتجنب React هذا العمل المكلف بسلاحها السري: الـ DOM الافتراضي (VDOM). الـ VDOM هو مجرد نسخة من الـ DOM محفوظة في ذاكرة JavaScript. إنها تلك الشجرة من كائنات React.createElement التي تحدثنا عنها.
إليك العملية، المعروفة باسم المطابقة (reconciliation):
- التصيير الأولي (Initial Render): عندما يتم تصيير
Counterلأول مرة، تبني React شجرة VDOM تصفdivيحتوي علىpبنص "لقد نقرت 0 مرات" وbutton. ثم تستخدم هذا لإنشاء عناصر الـ DOM الحقيقية على الصفحة. - تغيير الحالة (State Change): أنت تنقر على الزر. يتم استدعاء
setCount(1). - إعادة التصيير و VDOM جديد: تعيد React تشغيل دالة
Counter. وتبني شجرة VDOM جديدة. هذه الشجرة الجديدة مطابقة تقريبًا للقديمة، لكنpالآن يصف النص "لقد نقرت 1 مرة". - المقارنة (Diffing): تقوم خوارزمية "المقارنة" في React بمقارنة شجرة VDOM الجديدة بالقديمة. تتصفحهما بسرعة وتجد الاختلافات. في هذه الحالة، تجد اختلافًا واحدًا فقط: النص داخل وسم
<p>قد تغير. - تحديث مجمّع (Batch Update): تأخذ React هذا التغيير الصغير وتطبقه على الـ DOM الحقيقي. لا تلمس الـ
divأو الـbutton؛ هي فقط تحدث نص الـp.
عملية المقارنة والتصحيح هذه سريعة بشكل لا يصدق. عن طريق تجميع التحديثات وتقليل التلاعب المباشر بالـ DOM الحقيقي البطيء، تجعل React واجهات المستخدم تبدو سريعة وسلسة، حتى عندما تتغير بيانات معقدة بسرعة.
قصص من الواقع
قضية بيانات النموذج المختفية
كُلف مطور مبتدئ ببناء نموذج تسجيل مستخدم معقد متعدد الخطوات. كل خطوة كانت مكونًا مختلفًا. في مرحلة ما، احتاجوا إلى مربع اختيار (checkbox) صغير في الخطوة 2 لإظهار حقل إدخال إضافي بشكل شرطي. قام بتنفيذه، ولكن فجأة، ظهر لديه خطأ غريب: كلما نقر المستخدم على مربع الاختيار هذا، كانت جميع البيانات التي أدخلها في الخطوة 2 تختفي.
كان الجاني هو سوء فهم لخاصية "keys". لإظهار الحقل الإضافي، كان المطور يغير بنية JSX الخاصة به بطريقة جعلت React تعتقد أن مكون النموذج بأكمله للخطوة 2 هو مكون جديد ومختلف. بدون وجود key prop ثابت لتعريفه، دمرت React المكون القديم (مع كل حالته "state"، أي إدخالات المستخدم) وركّبت مكونًا جديدًا تمامًا في مكانه.
الدرس المستفاد: خاصية key ليست فقط للقوائم. إنها هوية ثابتة للمكون. فهم كيفية استخدام React للـ key لتتبع المكونات عبر عمليات إعادة التصيير أمر حاسم للحفاظ على الحالة وتجنب عمليات إعادة التعيين غير المتوقعة.
مطرقة الـ API اللانهائية
كان فريق يبني لوحة تحكم تعرض بيانات الأسهم الحية. كتب مطور مكونًا لجلب أحدث سعر لسهم معين باستخدام useEffect hook. بدا الكود شيئًا كهذا:
useEffect(() => {
fetchStockPrice(stockSymbol).then(price => {
setPrice(price); // تحديث الحالة بالسعر الجديد
});
}); // يا إلهي، لا توجد مصفوفة اعتماديات!
بمجرد تحميل المكون، تجمد التطبيق، وبدأت مروحة الكمبيوتر المحمول للمطور تدور بأقصى سرعة، وأظهرت مراقبة الشبكة آلاف الطلبات المتطابقة لـ API تُطلق في كل ثانية. لقد أنشأوا حلقة لا نهائية (infinite loop). قام useEffect بجلب البيانات واستدعى setPrice. تسبب تحديث الحالة في إعادة تصيير المكون. ولأن useEffect لم يكن لديه مصفوفة اعتماديات (dependency array)، فقد تم تشغيله مرة أخرى بعد كل عملية تصيير، مما أدى إلى جلب البيانات مرة أخرى، وتحديث الحالة مرة أخرى، مما تسبب في إعادة تصيير أخرى، وهكذا إلى ما لا نهاية.
الدرس المستفاد: خطافات React مثل useEffect هي أدوات قوية ولكنها دقيقة. يجب عليك توفير "مصفوفة اعتماديات" لإخبار React متى يجب إعادة تشغيل التأثير. تركها فارغة يعني "شغّل بعد كل عملية تصيير"، وهي وصفة لكارثة مع تحديثات الحالة.
أخطاء وفخاخ شائعة
- تعديل الحالة (state) بشكل مباشر. لا تقم أبدًا بعمل
myState.prop = 'value'أوmyArray.push('item'). يعتمد كشف التغييرات في React على هويات الكائنات والمصفوفات. عندما تستدعيsetState، يجب عليك توفير كائن أو مصفوفة جديدة (على سبيل المثال، باستخدام صيغة النشر...) حتى تلاحظ React التغيير. - نسيان
keys في القوائم. عندما تقوم بتصيير قائمة من العناصر باستخدام.map()، يجب أن تعطي كل عنصرkeyprop فريدًا ومستقرًا (مثلitem.id). هذه هي الطريقة التي يمكن بها لخوارزمية المقارنة في React تتبع العناصر التي تحركت أو أضيفت أو أزيلت بكفاءة. استخدام فهرس المصفوفة كمفتاح هو نمط سيء شائع يمكن أن يؤدي إلى أخطاء. - التعامل مع JSX كأنه HTML. إنه قريب، ولكنه ليس هو نفسه. يجب عليك استخدام
classNameبدلاً منclass، وhtmlForبدلاً منfor، و camelCase لخصائص الأنماط (backgroundColorوليسbackground-color). أيضًا، يجب إغلاق جميع الوسوم، لذا الصورة تكون<img ... />، وليس<img>. - الاعتقاد بأن الـ Hooks سحرية. تتبع الـ Hooks مثل
useStateوuseEffectقواعد صارمة (على سبيل المثال، لا يمكن استدعاؤها إلا في المستوى الأعلى من مكون دالة). إنها تعمل بالاعتماد على ترتيب استدعاء ثابت بين عمليات التصيير. محاولة استدعائها داخل الحلقات أو الشروط ستؤدي إلى كسر تطبيقك.
لماذا يجب أن تكون تحت أنظارك
React ليست مجرد مكتبة؛ إنها نظام بيئي (ecosystem) وطريقة تفكير. إنها تشغل جزءًا كبيرًا من الويب الحديث، من المواقع الضخمة مثل Instagram و Netflix إلى عدد لا يحصى من الشركات الناشئة والأدوات الداخلية. تعلم React يعني تعلم بنية قائمة على المكونات تعزز الكود القابل لإعادة الاستخدام والاختبار والصيانة.
حتى لو لم تستخدم React يوميًا، فإن فهم مبادئها الأساسية — واجهة المستخدم التصريحية (declarative UI)، وإدارة الحالة (state management)، والـ VDOM — سيجعلك مطورًا أفضل. لقد أثرت هذه المفاهيم على كل إطار عمل حديث آخر لواجهات المستخدم، من Vue إلى Svelte. علاوة على ذلك، مع React Native، يمكن استخدام نفس المهارات والمبادئ لبناء تطبيقات محمولة أصلية لـ iOS و Android. إنها نموذج قوي ضروري لأي مطور يعمل على واجهات المستخدم اليوم.
تعمق أكثر
- وثائق React (الرسمية) - الوثائق الرسمية هي أفضل مكان للبدء، مع برنامج تعليمي شامل وشروحات عميقة.
- مقدمة إلى JSX (وثائق React) - الشرح الرسمي لـ JSX وكيف يختلف عن HTML.
- المطابقة (Reconciliation) (وثائق React) - الخوارزمية التي تستخدمها React لمقارنة شجرتي VDOM وتحديث واجهة المستخدم بكفاءة.
- قواعد الـ Hooks (وثائق React) - القواعد الأساسية التي يجب اتباعها عند استخدام الـ Hooks مثل
useStateوuseEffect. - MDN: مقدمة إلى الـ DOM - لفهم ما تتلاعب به React، من الضروري فهم نموذج كائن المستند (DOM) نفسه.
كيف يفهم المحرر الشيفرة
ألوان الصياغة تأتي من محلل اللغة، لا من تشغيل البرنامج. يتعرف CodeMirror وLezer على الرموز والبنية محليًا في المتصفح. تجمع الاقتراحات الكلمات المحجوزة والأسماء المحلية عند دعمها والمقاطع الجاهزة ومرجعًا مدمجًا صغيرًا؛ ويكشف المرور فوق رمز موثق عن توقيعه. الاقتراح لا يضمن وجود الواجهة في بيئة التنفيذ. يكتشف المحلل أخطاء الصياغة عند دعمها، لكنه لا يحل محل فحص الأنواع أو التشغيل: قد تفشل شيفرة سليمة نحويًا. يستخدم Ruby وLua أوضاعًا قائمة على الرموز وتشخيصات أكثر محدودية. لا يوجد خادم لغة بعيد، ولا تحتاج الشيفرة إلى مغادرة الجهاز.
CodeMirror & Lezer: MIT.