React: النماذج والمكونات الخاضعة للرقابة

آخر تحديث: 2026-08-26

تُعد النماذج بمثابة جسر بين تطبيقات الويب والمستخدمين. في React، لم تعد النماذج تقتصر على «ملئها وإرسالها» فحسب — بل «تتعرف React على كل حرف تكتبه». ويُطلق على هذا النمط اسم المكون الخاضع للتحكم.


1. ما ستتعلمه



2. تطور نموذج التسجيل

(1) المشكلة: صعوبة تتبع البيانات الواردة من النماذج غير الخاضعة للرقابة

أنشأت أليس نموذجًا لتسجيل المستخدمين يسترد البيانات باستخدام الطرق الأصلية:

HTML
<!-- HTML Form:Read data all at once upon submission -->
<form id="register-form">
  <input name="username" id="username" />
  <input type="email" name="email" id="email" />
  <input type="password" name="password" id="password" />
  <select name="role" id="role">
    <option value="developer">Developer</option>
    <option value="designer">Designer</option>
  </select>
  <button type="submit">Register</button>
</form>

<script>
  document.getElementById('register-form').addEventListener('submit', function(e) {
    e.preventDefault()
    // I'll go when I submit it. DOM Data from Zhongna
    const username = document.getElementById('username').value
    const email = document.getElementById('email').value
    const password = document.getElementById('password').value
    const role = document.getElementById('role').value
    // ... Manually verify each field
  })
</script>

السؤال:

(2) حلول للمكونات التي يتم التحكم فيها بواسطة React

JSX
function RegisterForm() {
  const [form, setForm] = React.useState({
    username: '',
    email: '',
    password: '',
    role: 'developer'
  })

  // Each input change React Everyone knows
  function handleChange(e) {
    const { name, value } = e.target
    setForm(prev => ({ ...prev, [name]: value }))
  }

  function handleSubmit(e) {
    e.preventDefault()
    console.log('Registration Data:', form)  // Retrieve the complete form data
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="username" value={form.username} onChange={handleChange} />
      <input name="email" value={form.email} onChange={handleChange} />
      <input name="password" value={form.password} onChange={handleChange} />
      <select name="role" value={form.role} onChange={handleChange}>
        <option value="developer">Developer</option>
        <option value="designer">Designer</option>
      </select>
      <button type="submit">Register</button>
    </form>
  )
}
▶ جرّب الكود

المزايا: يتتبع React كل حقل إدخال في الوقت الفعلي، مما يوفر التحقق من صحة البيانات ومزامنة البيانات في الوقت الفعلي، ويسترد البيانات الكاملة فور إرسالها.



3. المكونات الخاضعة للرقابة مقابل المكونات غير الخاضعة للرقابة

100%
graph TB
    subgraph "Controlled Components(Recommendations)"
        A1[User Input] --> B1[onChange Event]
        B1 --> C1[Update State]
        C1 --> D1[React Render Again]
        D1 --> E1[value = State]
    end
    
    subgraph "Uncontrolled Components"
        A2[User Input] --> B2[Update Directly DOM]
        B2 --> C2[Use when submitting ref Reading]
    end
    
    style C1 fill:#52c41a,color:#fff
    style B2 fill:#faad14,color:#000
البعد المكونات الخاضعة للرقابة المكونات غير الخاضعة للرقابة
مصدر البيانات حالة React (المصدر الوحيد للمعلومات) DOM نفسه
طريقة الوصول القراءة مباشرة من State يتطلب مرجعًا أو document.getElementById
التحقق من الصحة في الوقت الفعلي ✅ مدعوم أصلاً ❌ يتطلب مراقبة إضافية
المزامنة الفورية ✅ مزامنة البيانات في الوقت الفعلي ❌ تتطلب مزامنة يدوية
حالات الاستخدام معظم النماذج تحميل الملفات، والنماذج البسيطة التي تُملأ مرة واحدة

القاعدة: استخدم المكونات الخاضعة للرقابة في 90% من الحالات. ويجب أن يستخدم الملف <input type="file"> وحده مكونًا غير خاضع للرقابة.



4. قواعد بناء الجمل الموجهة لعناصر النماذج المختلفة

عنصر النموذج الخاصية المرتبطة قيمة onChange مثال
<input type="text"> value e.target.value <input value={name} onChange={e => setName(e.target.value)} />
<input type="number"> value Number(e.target.value) يجب تحويلها إلى قيم رقمية يدويًّا
<input type="checkbox"> checked e.target.checked قيمة منطقية، ليست قيمة
<input type="radio"> checked e.target.value مجمعة حسب الاسم؛ تحدد علامة «محدد» ما إذا كانت محددة أم لا
<textarea> value e.target.value يستخدم React القيمة، وليس نص العنصر التابع
<select> value e.target.value اختيارات متعددة multiple + مصفوفة
<input type="file"> ❌ غير خاضع للتحكم e.target.files[0] لا يمكن استخدامه إلا كخط غير خاضع للتحكم

(1) إدخال النص

JSX
function TextInputs() {
  const [name, setName] = React.useState('')
  const [bio, setBio] = React.useState('')
  const [age, setAge] = React.useState(18)

  return (
    <div>
      {/* Single-line text */}
      <label>Name:</label>
      <input
        type="text"
        value={name}
        onChange={e => setName(e.target.value)}
        placeholder="Please enter your name"
      />

      {/* Numbers */}
      <label>Age:</label>
      <input
        type="number"
        value={age}
        onChange={e => setAge(Number(e.target.value))}  // Convert to a number!
        min={0} max={150}
      />

      {/* Multi-line text */}
      <label>Personal Profile:</label>
      <textarea
        value={bio}
        onChange={e => setBio(e.target.value)}
        rows={4}
        placeholder="Introduce Yourself..."
      />
    </div>
  )
}
▶ جرّب الكود

ملاحظة: في React، استخدم الخاصية <textarea> بدلاً من محتوى النص الفرعي. قيمة e.target.value الخاصة بـ <input type="number"> هي سلسلة نصية؛ لذا تأكد من تحويلها إلى رقم.

(2) مربع الاختيار

JSX
function SelectInputs() {
  const [city, setCity] = React.useState('')
  const [hobbies, setHobbies] = React.useState([])

  return (
    <div>
      {/* Single-selection drop-down menu */}
      <label>City:</label>
      <select value={city} onChange={e => setCity(e.target.value)}>
        <option value="">-- Please select --</option>
        <option value="beijing">Beijing</option>
        <option value="shanghai">Shanghai</option>
        <option value="shenzhen">Shenzhen</option>
      </select>

      {/* Multiple-choice drop-down list */}
      <label>Hobbies:</label>
      <select
        multiple
        value={hobbies}
        onChange={e => {
          const selected = Array.from(e.target.options)
            .filter(o => o.selected)
            .map(o => o.value)
          setHobbies(selected)
        }}
      >
        <option value="coding">Programming</option>
        <option value="reading">Read</option>
        <option value="gaming">Games</option>
      </select>
    </div>
  )
}
▶ جرّب الكود

(3) مربعات الاختيار وأزرار الاختيار

JSX
function CheckboxAndRadio() {
  const [agree, setAgree] = React.useState(false)
  const [gender, setGender] = React.useState('')
  const [interests, setInterests] = React.useState([])

  // Multiple Choice:Toggle Inclusion/Exclusion
  function toggleInterest(value) {
    setInterests(prev =>
      prev.includes(value)
        ? prev.filter(i => i !== value)
        : [...prev, value]
    )
  }

  return (
    <div>
      {/* Single checkbox */}
      <label>
        <input
          type="checkbox"
          checked={agree}
          onChange={e => setAgree(e.target.checked)}  // Please note that checked No value
        />
        Agree to the User Agreement
      </label>
      {agree && <p style={{ color: '#52c41a' }}>✅ Agreed</p>}

      {/* Radio Button Group:The same one name */}
      <p>Gender:</p>
      {['Male', 'Female', 'Other'].map(g => (
        <label key={g} style={{ marginRight: '12px' }}>
          <input
            type="radio"
            name="gender"
            value={g}
            checked={gender === g}
            onChange={e => setGender(e.target.value)}
          />
          {g}
        </label>
      ))}

      {/* Multiple-Selection Checkbox Group */}
      <p>Areas of Interest:</p>
      {['Front End', 'Backend', 'AI', 'Design'].map(item => (
        <label key={item} style={{ marginRight: '12px' }}>
          <input
            type="checkbox"
            checked={interests.includes(item)}
            onChange={() => toggleInterest(item)}
          />
          {item}
        </label>
      ))}
    </div>
  )
}

▶ المثال 1: جدول مرجعي سريع لأنواع عناصر النماذج

JSX
// ============================================
// Example:Controlled Syntax for All Common Form Elements
// ============================================

function FormElementReference() {
  const [text, setText] = React.useState('')
  const [number, setNumber] = React.useState(0)
  const [email, setEmail] = React.useState('')
  const [password, setPassword] = React.useState('')
  const [bio, setBio] = React.useState('')
  const [city, setCity] = React.useState('')
  const [agree, setAgree] = React.useState(false)
  const [gender, setGender] = React.useState('')
  const [file, setFile] = React.useState(null)

  return (
    <div>
      {/* Text Input */}
      <input type="text" value={text} onChange={e => setText(e.target.value)} />
      
      {/* Numeric Input */}
      <input type="number" value={number} onChange={e => setNumber(Number(e.target.value))} />
      
      {/* Enter your email address */}
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} />
      
      {/* Password Entry */}
      <input type="password" value={password} onChange={e => setPassword(e.target.value)} />
      
      {/* Multi-line text */}
      <textarea value={bio} onChange={e => setBio(e.target.value)} />
      
      {/* Drop-down menu */}
      <select value={city} onChange={e => setCity(e.target.value)}>
        <option value="">Please select</option>
        <option value="beijing">Beijing</option>
      </select>
      
      {/* Single checkbox */}
      <input type="checkbox" checked={agree} onChange={e => setAgree(e.target.checked)} />
      
      {/* Radio button */}
      <input type="radio" name="gender" value="male" checked={gender === 'male'}
        onChange={e => setGender(e.target.value)} />
      
      {/* File Upload(Uncontrolled) */}
      <input type="file" onChange={e => setFile(e.target.files[0])} />
      {/* Note:File input must be in uncontrolled mode. */}
    </div>
  )
}
▶ جرّب الكود

5. التحقق من صحة البيانات في النموذج

توقيت التحقق الحدث المُحفِّز المزايا العيوب السيناريوهات الموصى بها
أثناء الإدخال onChange ردود فعل فورية؛ يمكن للمستخدمين تصحيح الأخطاء على الفور يتم تشغيلها بشكل متكرر؛ يتم الإبلاغ عن الخطأ قبل أن ينتهي المستخدم من الكتابة قوة كلمة المرور، والتحقق من صحة اسم المستخدم
عند فقدان التركيز onBlur تقليل عوامل التشتيت؛ لا يتم التحقق من صحة البيانات إلا بعد الانتهاء لا يرى المستخدمون الأخطاء إلا بعد مغادرة الحقل معظم حقول النموذج
عند الإرسال onSubmit المراجعة النهائية والتحقق الموحد لا يكتشف المستخدم وجود خطأ إلا بعد ملء النموذج التحقق الاحتياطي النهائي

(1) التحقق في الوقت الفعلي

JSX
function ValidatedForm() {
  const [email, setEmail] = React.useState('')
  const [password, setPassword] = React.useState('')
  const [errors, setErrors] = React.useState({})
  const [touched, setTouched] = React.useState({})  // Record which fields have been accessed

  function validate(fieldName, value) {
    const newErrors = { ...errors }
    
    if (fieldName === 'email') {
      if (!value) newErrors.email = 'The email address cannot be left blank.'
      else if (!value.includes('@')) newErrors.email = 'The email address format is incorrect.'
      else delete newErrors.email
    }
    
    if (fieldName === 'password') {
      if (!value) newErrors.password = 'The password cannot be empty.'
      else if (value.length < 6) newErrors.password = 'Password must be at least 6 chars'
      else if (!/[A-Z]/.test(value)) newErrors.password = 'Passwords must contain uppercase letters'
      else delete newErrors.password
    }

    setErrors(newErrors)
  }

  function handleBlur(e) {
    const { name, value } = e.target
    setTouched({ ...touched, [name]: true })
    validate(name, value)
  }

  function handleSubmit(e) {
    e.preventDefault()
    // Validate all fields before submitting
    const allErrors = {}
    if (!email || !email.includes('@')) allErrors.email = 'Please enter a valid email address'
    if (!password || password.length < 6) allErrors.password = 'Password must be at least 6 chars'
    
    if (Object.keys(allErrors).length > 0) {
      setErrors(allErrors)
      setTouched({ email: true, password: true })
      return
    }

    alert('Submission Successful!')
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '400px' }}>
      <div style={{ marginBottom: '16px' }}>
        <label>Email:</label>
        <input
          name="email"
          type="email"
          value={email}
          onChange={e => { setEmail(e.target.value); validate('email', e.target.value) }}
          onBlur={handleBlur}
          style={inputStyle(touched.email && errors.email)}
        />
        {touched.email && errors.email && (
          <p style={{ color: '#ff4d4f', fontSize: '12px', margin: '4px 0' }}>{errors.email}</p>
        )}
      </div>

      <div style={{ marginBottom: '16px' }}>
        <label>Password:</label>
        <input
          name="password"
          type="password"
          value={password}
          onChange={e => { setPassword(e.target.value); validate('password', e.target.value) }}
          onBlur={handleBlur}
          style={inputStyle(touched.password && errors.password)}
        />
        {touched.password && errors.password && (
          <p style={{ color: '#ff4d4f', fontSize: '12px', margin: '4px 0' }}>{errors.password}</p>
        )}
      </div>

      {/* Password Strength Indicator */}
      {password && (
        <div style={{ marginBottom: '16px' }}>
          <p style={{ fontSize: '12px', color: '#666' }}>
            Password Strength:
            <span style={{ color: password.length < 6 ? '#ff4d4f' : password.length < 10 ? '#faad14' : '#52c41a' }}>
              {password.length < 6 ? 'Weak' : password.length < 10 ? 'Medium' : 'Strong'}
            </span>
          </p>
          <div style={{ height: '4px', backgroundColor: '#f0f0f0', borderRadius: '2px' }}>
            <div style={{
              height: '100%',
              width: `${Math.min(100, (password.length / 12) * 100)}%`,
              backgroundColor: password.length < 6 ? '#ff4d4f' : password.length < 10 ? '#faad14' : '#52c41a',
              borderRadius: '2px',
              transition: 'width 0.3s'
            }} />
          </div>
        </div>
      )}

      <button type="submit" disabled={Object.keys(errors).length > 0}
        style={{
          width: '100%', padding: '10px',
          backgroundColor: Object.keys(errors).length > 0 ? '#d9d9d9' : '#1890ff',
          color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer'
        }}>
        Register
      </button>
    </form>
  )
}

function inputStyle(hasError) {
  return {
    width: '100%', padding: '8px',
    border: `1px solid ${hasError ? '#ff4d4f' : '#d9d9d9'}`,
    borderRadius: '4px',
    outline: 'none'
  }
}

(2) تخصيص الخطاف useForm

افصل منطق النموذج في «هوك» مخصص لتسهيل إعادة استخدامه:

JSX
// ============================================
// Example:Custom useForm Hook
// Features:Unified Management of Form Status、Verification、Submit
// ============================================

function useForm(initialValues, validateFn) {
  const [values, setValues] = React.useState(initialValues)
  const [errors, setErrors] = React.useState({})
  const [touched, setTouched] = React.useState({})

  // Handling Input Changes
  function handleChange(e) {
    const { name, value, type, checked } = e.target
    const newValue = type === 'checkbox' ? checked : value
    const newValues = { ...values, [name]: newValue }
    setValues(newValues)
    
    // Real-Time Verification
    if (validateFn) {
      const newErrors = validateFn(newValues)
      setErrors(newErrors)
    }
  }

  // Handling Loss of Focus
  function handleBlur(e) {
    const { name } = e.target
    setTouched({ ...touched, [name]: true })
  }

  // Process Submission
  function handleSubmit(callback) {
    return (e) => {
      e.preventDefault()
      // Mark all fields as checked
      const allTouched = Object.keys(values).reduce((acc, key) => {
        acc[key] = true
        return acc
      }, {})
      setTouched(allTouched)
      
      // Verification
      if (validateFn) {
        const newErrors = validateFn(values)
        setErrors(newErrors)
        if (Object.keys(newErrors).length > 0) return
      }
      
      callback(values)
    }
  }

  // Reset Form
  function reset() {
    setValues(initialValues)
    setErrors({})
    setTouched({})
  }

  return { values, errors, touched, handleChange, handleBlur, handleSubmit, reset }
}

// ---- Usage useForm ----
function LoginForm() {
  function validate(values) {
    const errors = {}
    if (!values.email) errors.email = 'Please enter your email address'
    else if (!values.email.includes('@')) errors.email = 'The email address format is incorrect.'
    if (!values.password) errors.password = 'Please enter your password'
    else if (values.password.length < 6) errors.password = 'Password must be at least 6 chars'
    return errors
  }

  const form = useForm({ email: '', password: '' }, validate)

  return (
    <form onSubmit={form.handleSubmit(data => console.log('Submit:', data))}
      style={{ maxWidth: '400px', margin: '0 auto' }}>
      <h2>Log In</h2>
      
      <input
        name="email"
        value={form.values.email}
        onChange={form.handleChange}
        onBlur={form.handleBlur}
        placeholder="Email"
        style={{ width: '100%', padding: '8px', marginBottom: '4px', border: `1px solid ${form.touched.email && form.errors.email ? '#ff4d4f' : '#d9d9d9'}`, borderRadius: '4px' }}
      />
      {form.touched.email && form.errors.email && (
        <p style={{ color: '#ff4d4f', fontSize: '12px', margin: '0 0 12px 0' }}>{form.errors.email}</p>
      )}

      <input
        name="password"
        type="password"
        value={form.values.password}
        onChange={form.handleChange}
        onBlur={form.handleBlur}
        placeholder="Password"
        style={{ width: '100%', padding: '8px', marginBottom: '4px', border: `1px solid ${form.touched.password && form.errors.password ? '#ff4d4f' : '#d9d9d9'}`, borderRadius: '4px' }}
      />
      {form.touched.password && form.errors.password && (
        <p style={{ color: '#ff4d4f', fontSize: '12px', margin: '0 0 12px 0' }}>{form.errors.password}</p>
      )}

      <button type="submit" style={{
        width: '100%', padding: '10px', backgroundColor: '#1890ff',
        color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer'
      }}>
        Log In
      </button>
      <button type="button" onClick={form.reset}
        style={{ width: '100%', padding: '8px', marginTop: '8px', cursor: 'pointer' }}>
        Reset
      </button>
    </form>
  )
}


6. مثال كامل: نموذج تسجيل المستخدم

JSX
// ============================================
// Complete Example:User Registration Form
// Features:Controlled Components + Real-Time Verification + Form Data Synchronization
// ============================================

function RegistrationForm() {
  const [form, setForm] = React.useState({
    username: '',
    email: '',
    password: '',
    confirmPassword: '',
    country: '',
    gender: '',
    acceptTerms: false
  })
  const [errors, setErrors] = React.useState({})

  function validate() {
    const newErrors = {}
    if (!form.username || form.username.length < 2)
      newErrors.username = 'Username must be at least 2 characters'
    if (!form.email || !form.email.includes('@'))
      newErrors.email = 'Please enter a valid email address'
    if (!form.password || form.password.length < 6)
      newErrors.password = 'Password must be at least 6 chars'
    if (form.password !== form.confirmPassword)
      newErrors.confirmPassword = 'The two passwords do not match'
    if (!form.country)
      newErrors.country = 'Please select a country'
    if (!form.acceptTerms)
      newErrors.acceptTerms = 'Please agree to the Terms of Service'
    return newErrors
  }

  function handleChange(e) {
    const { name, value, type, checked } = e.target
    const newValue = type === 'checkbox' ? checked : value
    const newForm = { ...form, [name]: newValue }
    setForm(newForm)
    setErrors(validate())
    // Interconnection:Automatically adjust password rule prompts after selecting a country
  }

  function handleSubmit(e) {
    e.preventDefault()
    const finalErrors = validate()
    setErrors(finalErrors)
    if (Object.keys(finalErrors).length === 0) {
      alert(`Registration Successful!Welcome ${form.username}`)
    }
  }

  function inputStyle(fieldName) {
    return {
      width: '100%', padding: '8px', marginBottom: '4px',
      border: `1px solid ${errors[fieldName] ? '#ff4d4f' : '#d9d9d9'}`,
      borderRadius: '4px'
    }
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '500px', margin: '0 auto' }}>
      <h2>📝 User Registration</h2>

      <input name="username" value={form.username} onChange={handleChange}
        placeholder="Username" style={inputStyle('username')} />
      {errors.username && <p style={errorStyle}>{errors.username}</p>}

      <input name="email" type="email" value={form.email} onChange={handleChange}
        placeholder="Email" style={inputStyle('email')} />
      {errors.email && <p style={errorStyle}>{errors.email}</p>}

      <input name="password" type="password" value={form.password} onChange={handleChange}
        placeholder="Password (at least 6 chars)" style={inputStyle('password')} />
      {errors.password && <p style={errorStyle}>{errors.password}</p>}

      <input name="confirmPassword" type="password" value={form.confirmPassword}
        onChange={handleChange} placeholder="Confirm Password" style={inputStyle('confirmPassword')} />
      {errors.confirmPassword && <p style={errorStyle}>{errors.confirmPassword}</p>}

      <select name="country" value={form.country} onChange={handleChange}
        style={inputStyle('country')}>
        <option value="">-- Select a Country --</option>
        <option value="CN">China</option>
        <option value="US">United States</option>
        <option value="JP">Japan</option>
        <option value="BR">Brazil</option>
        <option value="SA">Saudi Arabia</option>
      </select>
      {errors.country && <p style={errorStyle}>{errors.country}</p>}

      <div style={{ margin: '12px 0' }}>
        <p>Gender:</p>
        {['Male', 'Female', 'Other'].map(g => (
          <label key={g} style={{ marginRight: '16px' }}>
            <input type="radio" name="gender" value={g}
              checked={form.gender === g} onChange={handleChange} />
            {g}
          </label>
        ))}
      </div>

      <label style={{ display: 'block', margin: '12px 0' }}>
        <input type="checkbox" name="acceptTerms" checked={form.acceptTerms}
          onChange={handleChange} />
        I have read and agree to <a href="#">User Agreement</a>
      </label>
      {errors.acceptTerms && <p style={errorStyle}>{errors.acceptTerms}</p>}

      <button type="submit" style={{
        width: '100%', padding: '10px',
        backgroundColor: Object.keys(errors).length === 0 && form.username ? '#1890ff' : '#d9d9d9',
        color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer'
      }}>
        Register
      </button>
    </form>
  )
}

const errorStyle = {
  color: '#ff4d4f', fontSize: '12px',
  margin: '0 0 8px 0'
}

▶ المثال 2: ربط بيانات النموذج — ملء رموز المناطق تلقائيًا عند اختيار البلد

JSX
function PhoneForm() {
  const [form, setForm] = useState({ country: 'US', phone: '' })
  const areaCodes = { US: '+1', CN: '+86', JP: '+81', UK: '+44', BR: '+55' }

  function handleCountryChange(e) {
    const country = e.target.value
    setForm(prev => ({
      ...prev,
      country,
      phone: prev.phone.replace(/^\+\d+\s*/, '') + ' ',
    }))
  }

  function handleChange(e) {
    const { name, value } = e.target
    setForm(prev => ({ ...prev, [name]: value }))
  }

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>Phone Contact</h3>
      <select name="country" value={form.country} onChange={handleCountryChange}
        style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }}>
        {Object.keys(areaCodes).map(c => (
          <option key={c} value={c}>{c}</option>
        ))}
      </select>
      <div style={{ display: 'flex', gap: 8 }}>
        <input value={areaCodes[form.country]} readOnly style={{ width: 60, padding: 8, borderRadius: 4 }} />
        <input name="phone" value={form.phone} onChange={handleChange}
          placeholder="Phone number" style={{ flex: 1, padding: 8, borderRadius: 4 }} />
      </div>
      <p style={{ color: '#666', fontSize: 13, marginTop: 8 }}>
        Full: {areaCodes[form.country]} {form.phone.trim()}
      </p>
    </div>
  )
}
▶ جرّب الكود

▶ المثال 3: نموذج ديناميكي — إضافة/حذف أعضاء الفريق

JSX
function TeamForm() {
  const [members, setMembers] = useState([{ name: '', email: '', role: 'developer' }])

  function addMember() {
    setMembers(prev => [...prev, { name: '', email: '', role: 'developer' }])
  }

  function removeMember(index) {
    setMembers(prev => prev.filter((_, i) => i !== index))
  }

  function updateMember(index, field, value) {
    setMembers(prev => prev.map((m, i) => i === index ? { ...m, [field]: value } : m))
  }

  function handleSubmit(e) {
    e.preventDefault()
    console.log('Team:', members)
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: 500, margin: '0 auto' }}>
      <h3>Team Members</h3>
      {members.map((m, i) => (
        <div key={i} style={{ display: 'flex', gap: 8, marginBottom: 8, alignItems: 'center' }}>
          <input value={m.name} onChange={e => updateMember(i, 'name', e.target.value)}
            placeholder="Name" style={{ flex: 1, padding: 6, borderRadius: 4 }} />
          <input value={m.email} onChange={e => updateMember(i, 'email', e.target.value)}
            placeholder="Email" style={{ flex: 1, padding: 6, borderRadius: 4 }} />
          <select value={m.role} onChange={e => updateMember(i, 'role', e.target.value)}
            style={{ padding: 6, borderRadius: 4 }}>
            <option value="developer">Dev</option>
            <option value="designer">Design</option>
            <option value="manager">PM</option>
          </select>
          {members.length > 1 && (
            <button type="button" onClick={() => removeMember(i)}
              style={{ padding: '6px 10px', color: '#ff4d4f', border: 'none', cursor: 'pointer' }}>
              X
            </button>
          )}
        </div>
      ))}
      <button type="button" onClick={addMember}
        style={{ padding: '6px 16px', marginRight: 8, cursor: 'pointer' }}>
        + Add Member
      </button>
      <button type="submit" style={{ padding: '6px 16px', cursor: 'pointer' }}>Submit</button>
    </form>
  )
}

▶ المثال 4: معالج النماذج متعدد الخطوات

JSX
function MultiStepForm() {
  const [step, setStep] = useState(1)
  const [form, setForm] = useState({ name: '', email: '', password: '', city: '', agree: false })
  const [errors, setErrors] = useState({})

  function updateField(e) {
    const { name, value, type, checked } = e.target
    setForm(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value }))
  }

  function validate() {
    const errs = {}
    if (step === 1) {
      if (!form.name.trim()) errs.name = 'Name required'
      if (!form.email.includes('@')) errs.email = 'Valid email required'
    } else if (step === 2) {
      if (form.password.length < 6) errs.password = 'At least 6 characters'
    } else if (step === 3) {
      if (!form.city) errs.city = 'Select a city'
      if (!form.agree) errs.agree = 'Must agree to terms'
    }
    setErrors(errs)
    return Object.keys(errs).length === 0
  }

  function next() { if (validate()) setStep(s => Math.min(s + 1, 3)) }
  function back() { setStep(s => Math.max(s - 1, 1)) }

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>Step {step} of 3</h3>
      <div style={{ display: 'flex', gap: 4, marginBottom: 16 }}>
        {[1, 2, 3].map(s => (
          <div key={s} style={{ flex: 1, height: 4, borderRadius: 2,
            backgroundColor: s <= step ? '#1890ff' : '#f0f0f0' }} />
        ))}
      </div>
      {step === 1 && (
        <>
          <input name="name" value={form.name} onChange={updateField} placeholder="Name"
            style={{ width: '100%', padding: 8, marginBottom: 4, borderRadius: 4, border: errors.name ? '1px solid #ff4d4f' : undefined }} />
          {errors.name && <p style={{ color: '#ff4d4f', fontSize: 12 }}>{errors.name}</p>}
          <input name="email" value={form.email} onChange={updateField} placeholder="Email"
            style={{ width: '100%', padding: 8, marginBottom: 4, borderRadius: 4, border: errors.email ? '1px solid #ff4d4f' : undefined }} />
          {errors.email && <p style={{ color: '#ff4d4f', fontSize: 12 }}>{errors.email}</p>}
        </>
      )}
      {step === 2 && (
        <>
          <input name="password" type="password" value={form.password} onChange={updateField}
            placeholder="Password" style={{ width: '100%', padding: 8, marginBottom: 4, borderRadius: 4 }} />
          {errors.password && <p style={{ color: '#ff4d4f', fontSize: 12 }}>{errors.password}</p>}
        </>
      )}
      {step === 3 && (
        <>
          <select name="city" value={form.city} onChange={updateField}
            style={{ width: '100%', padding: 8, marginBottom: 4, borderRadius: 4 }}>
            <option value="">Select city</option>
            <option value="beijing">Beijing</option>
            <option value="shanghai">Shanghai</option>
          </select>
          {errors.city && <p style={{ color: '#ff4d4f', fontSize: 12 }}>{errors.city}</p>}
          <label style={{ display: 'block', marginTop: 8 }}>
            <input type="checkbox" name="agree" checked={form.agree} onChange={updateField} />
            I agree to the terms
          </label>
          {errors.agree && <p style={{ color: '#ff4d4f', fontSize: 12 }}>{errors.agree}</p>}
        </>
      )}
      <div style={{ marginTop: 16 }}>
        {step > 1 && <button type="button" onClick={back} style={{ marginRight: 8, cursor: 'pointer' }}>Back</button>}
        {step < 3 ? (
          <button type="button" onClick={next} style={{ cursor: 'pointer' }}>Next</button>
        ) : (
          <button type="button" onClick={() => { if (validate()) alert('Done!') }} style={{ cursor: 'pointer' }}>Submit</button>
        )}
      </div>
    </div>
  )
}

▶ المثال 5: المكونات غير المدارة — تحميل الملفات وuseRef

JSX
function FileUploadForm() {
  const fileInputRef = useRef(null)
  const [preview, setPreview] = useState(null)
  const [fileName, setFileName] = useState('')

  function handleSubmit(e) {
    e.preventDefault()
    const file = fileInputRef.current?.files[0]
    if (!file) { alert('Please select a file'); return }
    console.log('Uploading:', file.name, file.size, 'bytes')
    alert(`File "${file.name}" (${(file.size / 1024).toFixed(1)} KB) selected for upload`)
  }

  function handleFileChange(e) {
    const file = e.target.files[0]
    if (!file) { setPreview(null); setFileName(''); return }
    setFileName(file.name)
    if (file.type.startsWith('image/')) {
      const reader = new FileReader()
      reader.onload = (e) => setPreview(e.target.result)
      reader.readAsDataURL(file)
    } else {
      setPreview(null)
    }
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>File Upload (Uncontrolled)</h3>
      <div style={{ marginBottom: 12 }}>
        <input type="file" ref={fileInputRef} onChange={handleFileChange}
          accept="image/*,.pdf,.doc" style={{ marginBottom: 8 }} />
        {fileName && <p style={{ fontSize: 13, color: '#666' }}>Selected: {fileName}</p>}
      </div>
      {preview && (
        <div style={{ marginBottom: 12 }}>
          <img src={preview} alt="Preview" style={{ maxWidth: '100%', maxHeight: 200, borderRadius: 4, border: '1px solid #eee' }} />
        </div>
      )}
      <button type="submit" style={{ padding: '8px 24px', cursor: 'pointer' }}>Upload</button>
    </form>
  )
}
▶ جرّب الكود

❓ أسئلة شائعة

س بالنسبة لـ input[type="checkbox"]، هل يجب أن أستخدم checked أم value؟
ج بالنسبة لمربعات الاختيار، استخدم checked attribute (boolean), not value. To retrieve the new value in the corresponding onChange event, use e.target.checked. Similarly, for radio buttons, use checked.
س متى ينبغي إجراء التحقق من صحة النموذج؟
ج يخدم كل خيار من الخيارات الثلاثة غرضًا مختلفًا: ① التحقق من الصحة أثناء كتابة المستخدم (onChange) — يوفر ردود فعل فورية ولكنه يتم تشغيله بشكل متكرر؛ ② التحقق من صحة البيانات عند فقدان الحقل للتركيز (onBlur) — يقلل من الإزعاج من خلال عرض الأخطاء فقط بعد انتهاء المستخدم من ملء النموذج؛ ③ التحقق من صحة البيانات عند الإرسال (onSubmit) — يُعد بمثابة الفحص النهائي. نوصي باستخدام مزيج من onBlur وonSubmit، مع إضافة onChange إلى الحقول المهمة مثل اسم المستخدم للحصول على ردود فعل فورية.
س ما هي الطريقة العامة للتعامل مع handleChange عندما يحتوي النموذج على العديد من الحقول؟
ج استخدم e.target.name بالاقتران مع اسم الخاصية المحسوبة: const { name, value } = e.target; setForm(prev => ({...prev, [name]: value})). وبهذه الطريقة، يمكن لجميع <input name="xxx"> استخدام نفس handleChange. تتطلب خانات الاختيار معالجة خاصة: const newValue = type === 'checkbox' ? checked : value.
س كيف يمكنك التمييز بين المكونات الخاضعة للتحكم والمكونات غير الخاضعة للتحكم؟
ج هناك قاعدة بسيطة: إذا كانت هناك حاجة إلى التحقق من الصحة في الوقت الفعلي، أو التنسيق، أو العرض الشرطي → فاستخدم مكونًا خاضعًا للتحكم (مثل: تلميحات قوة كلمة المرور في الوقت الفعلي، والتنسيق التلقائي للمبالغ). إذا كان اهتمامك يقتصر على القيمة النهائية أو قراءة القيمة عند الإرسال فقط → فاستخدم مكونًا غير خاضع للرقابة (مثل: تحميل الملفات، ونماذج الاتصال البسيطة). <input type="file"> لا يمكنك استخدام سوى المكونات غير الخاضعة للرقابة لأن value الخاص بها هو للقراءة فقط. useForm يمكن لـ Hooks تغليف الكود النمطي للمكونات الخاضعة للرقابة.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (مستوى الصعوبة ⭐): أنشئ مكونًا باسم FeedbackForm يتضمن الاسم، والبريد الإلكتروني، والتقييم (خيارات القائمة المنسدلة: إيجابي/محايد/سلبي)، والمحتوى (منطقة نصية). عند الإرسال، اعرض جميع البيانات في نافذة تنبيه.
  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): قم بإنشاء مكون PasswordStrengthMeter يعرض قوة كلمة المرور (ضعيفة/متوسطة/قوية) في الوقت الفعلي. القواعد: ≥8 أحرف + أحرف كبيرة وصغيرة + أرقام = قوية؛ ≥6 أحرف = متوسطة؛ بخلاف ذلك = ضعيفة.
  3. التحدي (الصعوبة: ⭐⭐⭐): قم بإنشاء مكون DynamicForm يتيح للمستخدمين إضافة عناصر إلى النموذج أو حذفها ديناميكيًا (على سبيل المثال، «إضافة عدة أفراد من العائلة»). يجب أن يتضمن كل عنصر في النموذج ثلاثة حقول: الاسم، والعمر، والعلاقة. وعند الإرسال، يجب أن يُخرج المكون مصفوفة.
Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%