أساسيات React.js

النماذج والمكونات المتحكم فيها

18 دقيقة الدرس 10 من 40

فهم النماذج في React

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

المفهوم الأساسي: المكون المتحكم فيه هو عنصر نموذج يتم التحكم في قيمته بواسطة حالة React. يصبح React "المصدر الوحيد للحقيقة" لبيانات النموذج، مما يجعلها قابلة للتنبؤ وسهلة الإدارة.

المكونات المتحكم فيها مقابل غير المتحكم فيها

فهم الفرق بين المكونات المتحكم فيها وغير المتحكم فيها أمر بالغ الأهمية:

// غير متحكم فيه: DOM يدير القيمة function UncontrolledForm() { const inputRef = useRef(null); const handleSubmit = (e) => { e.preventDefault(); // الوصول إلى القيمة من DOM مباشرة console.log(inputRef.current.value); }; return ( <form onSubmit={handleSubmit}> <input type="text" ref={inputRef} /> <button type="submit">إرسال</button> </form> ); } // متحكم فيه: حالة React تدير القيمة function ControlledForm() { const [value, setValue] = useState(''); const handleSubmit = (e) => { e.preventDefault(); // القيمة دائماً متزامنة مع الحالة console.log(value); }; return ( <form onSubmit={handleSubmit}> <input type="text" value={value} onChange={(e) => setValue(e.target.value)} /> <button type="submit">إرسال</button> </form> ); }
أفضل ممارسة: استخدم المكونات المتحكم فيها في معظم الحالات. تمنحك السيطرة الكاملة على بيانات النموذج، وتمكن التحقق والعرض الشرطي، وتجعل التصحيح أسهل. استخدم المكونات غير المتحكم فيها فقط عند التكامل مع كود غير React أو عندما يكون الأداء حرجاً.

إدخال النص - المكون المتحكم فيه

عنصر النموذج الأكثر شيوعاً هو إدخال النص. إليك كيفية جعله متحكماً فيه:

import { useState } from 'react'; function NameForm() { const [name, setName] = useState(''); const handleSubmit = (event) => { event.preventDefault(); alert(`الاسم المُرسل: ${name}`); }; const handleChange = (event) => { // تحديث الحالة على كل ضغطة مفتاح setName(event.target.value); }; return ( <form onSubmit={handleSubmit}> <label> الاسم: <input type="text" value={name} onChange={handleChange} placeholder="أدخل اسمك" /> </label> <p>القيمة الحالية: {name}</p> <button type="submit">إرسال</button> </form> ); }

عنصر Textarea

في React، يستخدم <textarea> سمة value بدلاً من الأبناء، مما يجعله متسقاً مع عناصر الإدخال الأخرى:

function EssayForm() { const [essay, setEssay] = useState(''); const handleSubmit = (event) => { event.preventDefault(); alert(`تم إرسال المقال بـ ${essay.length} حرف`); }; return ( <form onSubmit={handleSubmit}> <label> المقال: <textarea value={essay} onChange={(e) => setEssay(e.target.value)} placeholder="اكتب مقالك هنا..." rows={5} /> </label> <p>عدد الأحرف: {essay.length}</p> <button type="submit">إرسال</button> </form> ); }

عنصر Select (القائمة المنسدلة)

يستخدم عنصر <select> أيضاً سمة value للتحكم في الخيار المحدد:

function FlavorForm() { const [flavor, setFlavor] = useState('coconut'); const handleSubmit = (event) => { event.preventDefault(); alert(`نكهتك المفضلة هي: ${flavor}`); }; return ( <form onSubmit={handleSubmit}> <label> اختر نكهتك المفضلة: <select value={flavor} onChange={(e) => setFlavor(e.target.value)}> <option value="grapefruit">جريب فروت</option> <option value="lime">ليمون</option> <option value="coconut">جوز الهند</option> <option value="mango">مانجو</option> </select> </label> <button type="submit">إرسال</button> </form> ); }

مربعات الاختيار وأزرار الراديو

تستخدم مربعات الاختيار وأزرار الراديو سمة checked بدلاً من value:

function PreferencesForm() { const [preferences, setPreferences] = useState({ newsletter: false, notifications: true, theme: 'light' }); const handleCheckboxChange = (event) => { const { name, checked } = event.target; setPreferences(prev => ({ ...prev, [name]: checked })); }; const handleRadioChange = (event) => { setPreferences(prev => ({ ...prev, theme: event.target.value })); }; return ( <form> {/* مربعات الاختيار */} <label> <input type="checkbox" name="newsletter" checked={preferences.newsletter} onChange={handleCheckboxChange} /> الاشتراك في النشرة الإخبارية </label> <br /> <label> <input type="checkbox" name="notifications" checked={preferences.notifications} onChange={handleCheckboxChange} /> تمكين الإشعارات </label> <br /> {/* أزرار الراديو */} <p>المظهر:</p> <label> <input type="radio" value="light" checked={preferences.theme === 'light'} onChange={handleRadioChange} /> فاتح </label> <label> <input type="radio" value="dark" checked={preferences.theme === 'dark'} onChange={handleRadioChange} /> داكن </label> <p>التفضيلات الحالية: {JSON.stringify(preferences, null, 2)}</p> </form> ); }
خطأ شائع: نسيان ربط value بالحالة يجعل الإدخال غير متحكم فيه. استخدم دائماً value={state} أو checked={state} مع معالج onChange للحفاظ على الإدخال متحكماً فيه.

معالجة إدخالات متعددة

عند التعامل مع إدخالات متعددة، يمكنك استخدام دالة معالج واحدة مع أسماء خصائص محسوبة:

function RegistrationForm() { const [formData, setFormData] = useState({ username: '', email: '', password: '', age: '', country: 'usa' }); // معالج واحد لجميع الإدخالات const handleChange = (event) => { const { name, value, type, checked } = event.target; setFormData(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value })); }; const handleSubmit = (event) => { event.preventDefault(); console.log('تم إرسال النموذج:', formData); }; return ( <form onSubmit={handleSubmit}> <input type="text" name="username" value={formData.username} onChange={handleChange} placeholder="اسم المستخدم" /> <br /> <input type="email" name="email" value={formData.email} onChange={handleChange} placeholder="البريد الإلكتروني" /> <br /> <input type="password" name="password" value={formData.password} onChange={handleChange} placeholder="كلمة المرور" /> <br /> <input type="number" name="age" value={formData.age} onChange={handleChange} placeholder="العمر" /> <br /> <select name="country" value={formData.country} onChange={handleChange}> <option value="usa">الولايات المتحدة</option> <option value="uk">المملكة المتحدة</option> <option value="canada">كندا</option> </select> <br /> <button type="submit">تسجيل</button> </form> ); }

التحقق من النموذج

تجعل المكونات المتحكم فيها التحقق واضحاً ومباشراً. يمكنك التحقق على كل تغيير أو عند الإرسال:

function LoginForm() { const [formData, setFormData] = useState({ email: '', password: '' }); const [errors, setErrors] = useState({}); const validateField = (name, value) => { switch(name) { case 'email': if (!value) return 'البريد الإلكتروني مطلوب'; if (!/\S+@\S+\.\S+/.test(value)) return 'البريد الإلكتروني غير صالح'; return ''; case 'password': if (!value) return 'كلمة المرور مطلوبة'; if (value.length < 6) return 'يجب أن تكون كلمة المرور 6 أحرف على الأقل'; return ''; default: return ''; } }; const handleChange = (event) => { const { name, value } = event.target; // تحديث بيانات النموذج setFormData(prev => ({ ...prev, [name]: value })); // التحقق عند التغيير const error = validateField(name, value); setErrors(prev => ({ ...prev, [name]: error })); }; const handleSubmit = (event) => { event.preventDefault(); // التحقق من جميع الحقول const newErrors = { email: validateField('email', formData.email), password: validateField('password', formData.password) }; setErrors(newErrors); // التحقق من وجود أخطاء const hasErrors = Object.values(newErrors).some(error => error); if (!hasErrors) { console.log('النموذج صالح، جاري الإرسال:', formData); // إرسال النموذج } }; return ( <form onSubmit={handleSubmit}> <div> <input type="email" name="email" value={formData.email} onChange={handleChange} placeholder="البريد الإلكتروني" /> {errors.email && ( <p style={{ color: 'red' }}>{errors.email}</p> )} </div> <div> <input type="password" name="password" value={formData.password} onChange={handleChange} placeholder="كلمة المرور" /> {errors.password && ( <p style={{ color: 'red' }}>{errors.password}</p> )} </div> <button type="submit">تسجيل الدخول</button> </form> ); }
استراتيجية التحقق: لتحسين تجربة المستخدم، تحقق عند blur (عندما يغادر المستخدم الحقل) بدلاً من كل ضغطة مفتاح. تحقق من جميع الحقول عند الإرسال لاصطياد أي أخطاء مفقودة.

إدخال الملف

إدخالات الملفات دائماً غير متحكم فيها في React لأن قيمتها يمكن تعيينها فقط من قبل المستخدم، وليس برمجياً:

function FileUploadForm() { const [selectedFile, setSelectedFile] = useState(null); const fileInputRef = useRef(null); const handleFileChange = (event) => { const file = event.target.files[0]; if (file) { setSelectedFile({ name: file.name, size: file.size, type: file.type }); } }; const handleSubmit = (event) => { event.preventDefault(); if (selectedFile) { console.log('رفع الملف:', selectedFile); // تنفيذ رفع الملف } }; const handleClear = () => { setSelectedFile(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; return ( <form onSubmit={handleSubmit}> <input type="file" ref={fileInputRef} onChange={handleFileChange} accept="image/*,.pdf" /> {selectedFile && ( <div> <p>المحدد: {selectedFile.name}</p> <p>الحجم: {(selectedFile.size / 1024).toFixed(2)} كيلوبايت</p> <p>النوع: {selectedFile.type}</p> <button type="button" onClick={handleClear}>مسح</button> </div> )} <button type="submit" disabled={!selectedFile}> رفع </button> </form> ); }
مكتبات النماذج: للنماذج المعقدة ذات التحقق المتقدم، فكر في استخدام مكتبات النماذج مثل Formik أو React Hook Form أو React Final Form. توفر التحقق المدمج ومعالجة الأخطاء وتقلل من التعليمات البرمجية النمطية.

تمرين 1: نموذج اتصال مع التحقق

أنشئ نموذج اتصال بالمتطلبات التالية:

  • الحقول: الاسم (مطلوب)، البريد الإلكتروني (مطلوب، تنسيق صالح)، الهاتف (اختياري، 10 أرقام)، الرسالة (مطلوبة، 10 أحرف كحد أدنى)
  • إظهار رسائل الخطأ فقط بعد أن يغادر المستخدم الحقل (onBlur)
  • تعطيل زر الإرسال إذا كان النموذج يحتوي على أخطاء
  • إظهار عدد الأحرف لحقل الرسالة
  • بعد الإرسال الناجح، إظهار رسالة نجاح ومسح النموذج
  • إضافة زر إعادة تعيين لمسح جميع الحقول

تلميح: استخدم حالة touched لتتبع الحقول التي تفاعل معها المستخدم.

تمرين 2: نموذج استطلاع ديناميكي

ابنِ نموذج استطلاع يتغير بناءً على استجابات المستخدم:

  • ابدأ بـ: الاسم، العمر، حالة التوظيف (قائمة منسدلة: موظف/طالب/عاطل)
  • إذا "موظف": أظهر حقول اسم الشركة والمنصب
  • إذا "طالب": أظهر حقول اسم المدرسة والتخصص
  • جميع المستخدمين: أظهر قائمة مربعات اختيار للاهتمامات (الرياضة، الموسيقى، القراءة، الألعاب، السفر)
  • بناءً على الاهتمامات المحددة، أظهر أسئلة متابعة (مثلاً، إذا الموسيقى: النوع المفضل)
  • احسب واعرض نسبة إكمال الاستطلاع
  • صفحة ملخص تظهر جميع الإجابات قبل الإرسال

إضافي: أضف التنقل بين الأقسام مع التحقق في كل خطوة.

تمرين 3: نموذج الدفع للتجارة الإلكترونية

أنشئ نموذج دفع متعدد الخطوات:

  • الخطوة 1: معلومات الشحن (الاسم الكامل، العنوان، المدينة، الولاية، الرمز البريدي، البلد)
  • الخطوة 2: طريقة الدفع (راديو: بطاقة ائتمان/PayPal/تحويل بنكي)
  • إذا بطاقة ائتمان: أظهر حقول رقم البطاقة والانتهاء وCVV
  • الخطوة 3: مراجعة الطلب مع جميع التفاصيل والسعر الإجمالي
  • التحقق من كل خطوة قبل السماح بالخطوة التالية
  • إضافة أزرار "رجوع" و"التالي"
  • حفظ بيانات النموذج في localStorage للاستمرار بين التحديثات
  • إضافة مؤشر تقدم يوضح الخطوة الحالية

تلميح: استخدم كائنات حالة متعددة، واحد لكل خطوة، وعداد خطوات.