فهم State وProps في React من خلال تطبيق قائمة مهام

فهم State وProps في React من خلال تطبيق قائمة مهام

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



الفكرة الأساسية

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

مثال برمجي عملي

import { useState } from "react";

function TaskList() {
  const [tasks, setTasks] = useState([]);
  const [title, setTitle] = useState("");

  function addTask(event) {
    event.preventDefault();
    const cleanTitle = title.trim();
    if (!cleanTitle) return;

    setTasks((current) => [
      ...current,
      { id: crypto.randomUUID(), title: cleanTitle, done: false }
    ]);
    setTitle("");
  }

  function toggleTask(id) {
    setTasks((current) => current.map((task) =>
      task.id === id ? { ...task, done: !task.done } : task
    ));
  }

  return (
    <section>
      <form onSubmit={addTask}>
        <input value={title} onChange={(event) => setTitle(event.target.value)} />
        <button type="submit">إضافة</button>
      </form>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>
            <label>
              <input type="checkbox" checked={task.done} onChange={() => toggleTask(task.id)} />
              {task.title}
            </label>
          </li>
        ))}
      </ul>
    </section>
  );
}

export default TaskList;

شرح المثال

تحتفظ TaskList بمصفوفة tasks وبقيمة الحقل title. استخدمنا دالة التحديث التي تستقبل current حتى لا نعتمد على قيمة قديمة عندما تأتي تحديثات متقاربة. عند إضافة مهمة ننشئ مصفوفة جديدة باستخدام spread، وعند تغيير الحالة نستخدم map وننشئ نسخة من المهمة المطابقة. في JSX نستخدم key ثابتاً لكل عنصر حتى تتتبع React العناصر بكفاءة. تحويل علامات JSX إلى كيانات HTML ضروري داخل ملف Blogger، بينما يعيدها محرر React إلى شكلها عند النسخ إلى ملف المشروع.

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

طريقة العمل قبل كتابة الكود

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

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

اختبار المثال في حالات مختلفة

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

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

ملاحظات تتعلق بجودة الكود

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

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

متى تعرف أن الحل يحتاج إلى تطوير؟

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

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

أخطاء ينبغي تجنبها

من الأخطاء تعديل المصفوفة مباشرة باستخدام push ثم تمرير المرجع نفسه، أو استخدام index كـ key في قائمة قابلة لإعادة الترتيب. لا تستدع setTasks بالاعتماد على tasks القديمة عندما يمكن أن تتقارب الأحداث. كما لا تضع state في مكون بعيد عن المكونات التي تحتاجها، ولا تمرر props عبر طبقات كثيرة بلا حاجة؛ عندها راجع التصميم أو استخدم سياقاً بحذر.

خطوة تالية مناسبة

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

تجنب الحالة المكررة في React

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

الخلاصة

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

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

تعليقات