React: أساسيات State: useState

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

الحالة (State) هي ذاكرة المكون. إذا كانت الخصائص (Props) هي معلمات تُعطى لك من قبل عناصر أخرى (للقراءة فقط)، فإن الحالة (State) هي دفتر ملاحظاتك الصغير الخاص (القابل للتعديل). أما useState فهي «رقاقة الذاكرة» التي يثبتها React في المكون.


1. ما ستتعلمه



2. تأملات أثارها عداد

(1) المشكلة: المتغيرات العادية لا تؤدي إلى تحديث واجهة المستخدم

يريد بوب إنشاء عداد بسيط: عندما تنقر على الزر، يزداد الرقم بمقدار 1.

JSX
// ❌ Ordinary Variables,Will not trigger a re-render
function Counter() {
  let count = 0  // Ordinary Variables

  function handleClick() {
    count = count + 1  // Variable changed, but UI won't update!
    console.log(count) // Console Display 1, 2, 3... But the page always displays 0
  }

  return (
    <div>
      <p>Count:{count}</p>  {/* Always Show 0 */}
      <button onClick={handleClick}>+1</button>
    </div>
  )
}
▶ جرّب الكود

اكتشف بوب مشكلةً وهي: المتغيرات العادية لا تُعلم React بضرورة تحديث واجهة المستخدم. في كل مرة يتم فيها إعادة عرض المكون، يتم إعادة تعيين count إلى 0.

(2) حل باستخدام useState

JSX
// ✅ Usage State,React It automatically tracks changes and updates UI
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)  // Initial value 0

  function handleClick() {
    setCount(count + 1)  // Update Status → React Automatic Re-rendering
  }

  return (
    <div>
      <p>Count:{count}</p>  {/* Automatically update with each click */}
      <button onClick={handleClick}>+1</button>
    </div>
  )
}
▶ جرّب الكود

قيمة الإرجاع: تُرجع useState مصفوفة [current value, setter function]. بعد استدعاء setCount، يقوم React تلقائيًا بإعادة عرض المكون لعرض أحدث القيم.



3. المفاهيم الأساسية لـ useState

(1) تحليل المهمة

JSX
import { useState } from 'react'

// useState(initialValue) Back [value, setValue]
const [count, setCount] = useState(0)
//      ^      ^          ^
//      |      |          └─ Initial value(Applies only to the first render)
//      |      └─ Update Function(Triggers a re-render after being called)
//      └─ Current status value(Get the latest value on every render)
▶ جرّب الكود
القسم الوصف
count قيمة الحالة الحالية؛ يتم استرداد أحدث قيمة مع كل عملية عرض
setCount دالة التحديث: عند استدعائها → يقوم React بإعادة عرض المكون
useState(0) القيمة الأولية (تُستخدم أثناء عملية العرض الأولى؛ ويتم تجاهلها في عمليات العرض اللاحقة)

(2) آلية التحديث

JSX
function Example() {
  const [count, setCount] = useState(0)

  function handleClick() {
    setCount(count + 1)  // 1. Request Update
    // Note:Here count Still the old value!
    console.log(count)    // Output 0,No 1
  }

  return <button onClick={handleClick}>{count}</button>
}
▶ جرّب الكود

المفهوم الأساسي: setCount(count + 1) تعني «مطالبة React بتحديث count عند عملية العرض التالية»، وليس «تغيير count على الفور». وهذا ما يُسمى التحديث غير المتزامن.

(3) تحديثات وظيفية

إذا كانت الحالة الجديدة تعتمد على الحالة القديمة، فيجب عليك استخدام التحديثات الوظيفية:

JSX
function Counter() {
  const [count, setCount] = useState(0)

  function handleClick() {
    // ✅ Recommendations:Functional Updates
    setCount(prev => prev + 1)
    setCount(prev => prev + 1)  // Called twice in a row, count becomes +2
  }

  // ❌ Not recommended:Update Directly(Chain calls take effect only once)
  function handleBadClick() {
    setCount(count + 1)
    setCount(count + 1)  // Called twice count All are old values,The result was only +1
  }

  return <button onClick={handleClick}>Currently:{count}</button>
}
▶ جرّب الكود
الطريقة الصيغة الخصائص
التحديث المباشر setCount(count + 1) بسيط، لكن البيانات تُفقد عند إجراء مكالمات متتالية
تحديث وظيفي setCount(prev => prev + 1) دقيق؛ يعمل بشكل صحيح حتى مع المكالمات المتتالية


4. تحديث المصفوفات والكائنات (الثبات)

يتطلب React عدم تعديل الحالة بشكل مباشر. لا تستخدم أبدًا state.push() أو state.name = 'xxx'؛ بل قم بدلاً من ذلك بإنشاء مصفوفة أو كائن جديد لاستبدال القيمة القديمة.

(1) العناصر المطلوب تحديثها

JSX
function UserEditor() {
  const [user, setUser] = useState({
    name: 'Alice',
    age: 28,
    email: 'alice@example.com'
  })

  function updateName(newName) {
    // ❌ Error:Edit directly state Object
    user.name = newName
    // React Changes will not be detected,UI Will not be updated

    // ✅ Correct:Create a new object
    setUser({ ...user, name: newName })
  }

  function updateAge(newAge) {
    // ✅ Expand Operator:Keep the other fields,Update Only age
    setUser({ ...user, age: newAge })
  }

  function resetUser() {
    // ✅ Reset to default values
    setUser({ name: '', age: 0, email: '' })
  }

  return (
    <div>
      <p>{user.name} - {user.age} years old</p>
      <button onClick={() => updateName('Bob')}>Change Name Bob</button>
      <button onClick={() => updateAge(user.age + 1)}>Age +1</button>
    </div>
  )
}
▶ جرّب الكود

(2) تحديث المصفوفة

JSX
function ShoppingCart() {
  const [items, setItems] = useState([
    { id: 1, name: 'Apple', qty: 2 },
    { id: 2, name: 'Banana', qty: 1 }
  ])

  // Add:Create a new array using the spread operator
  function addItem(name) {
    setItems([...items, { id: Date.now(), name, qty: 1 }])
  }

  // Delete:use  filter Create a new array
  function removeItem(id) {
    setItems(items.filter(item => item.id !== id))
  }

  // Update:use  map Create a new array
  function updateQty(id, newQty) {
    setItems(items.map(item =>
      item.id === id ? { ...item, qty: newQty } : item
    ))
  }

  return (
    <div>
      <button onClick={() => addItem('Orange')}>Add orange</button>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.name} × {item.qty}
            <button onClick={() => updateQty(item.id, item.qty + 1)}>+</button>
            <button onClick={() => removeItem(item.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  )
}
▶ جرّب الكود

▶ مثال: جدول مرجعي سريع لعمليات المصفوفات

JSX
// ============================================
// Example:React Array Operations in C 6 Common Scenarios
// ============================================

const [arr, setArr] = useState([1, 2, 3])

// 1. Append to the end
setArr([...arr, 4])           // [1, 2, 3, 4]

// 2. Add to the beginning
setArr([0, ...arr])           // [0, 1, 2, 3]

// 3. Insert in the middle
const insertAt = 1
setArr([...arr.slice(0, insertAt), 99, ...arr.slice(insertAt)])
                              // [1, 99, 2, 3]

// 4. Delete Element(filter)
setArr(arr.filter(n => n !== 2))  // [1, 3]

// 5. Update Element(map)
setArr(arr.map(n => n === 2 ? 22 : n))  // [1, 22, 3]

// 6. Sort(Copy first, then sort)
setArr([...arr].sort((a, b) => b - a))  // [3, 2, 1]
▶ جرّب الكود

5. متغيرات الحالة المتعددة

يمكن أن يحتوي المكون على متغيرات حالة متعددة. يُنصح بتقسيمها وفقًا للمنطق بدلاً من حشرها في كائن واحد كبير:

JSX
function RegistrationForm() {
  // ✅ Recommendations:Logical Breakdown
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [isSubmitting, setIsSubmitting] = useState(false)
  const [errors, setErrors] = useState({})

  // ❌ Not recommended:All states are stored in a single object
  const [form, setForm] = useState({
    name: '', email: '', password: '',
    isSubmitting: false, errors: {}
  })
  // Expand the entire object when updating,Fields that are easy to overlook
}
▶ جرّب الكود
الاستراتيجية المزايا العيوب
تقسيمها إلى عدة useState تحديثات دقيقة، وآمنة من حيث الأنواع، وسهلة القراءة وجود عدد كبير جدًا من المتغيرات عند وجود الكثير من الحالات
مدمجة في useState واحد ملائمة لتحديث حقول متعددة في آن واحد صيغة التحديث معقدة ومن السهل نسيان توسيعها

توصية: استخدم عدة مثيلات من useState للحالات المستقلة (مثل حقول الإدخال، ومؤشرات التحميل، ورسائل الخطأ)؛ أما الحقول المرتبطة منطقياً (مثل جميع الحقول في ملف تعريف المستخدم) فيمكن دمجها في كائن واحد.



6. النهوض بالدولة

عندما تحتاج عدة مكونات إلى مشاركة الحالة نفسها، قم بنقل الحالة إلى أقرب مكون أب مشترك بينها.

100%
graph TB
    subgraph "Before the status upgrade(Data is not shared)"
        A[Parent Component App] --- B[Child component A<br/>Have their own count]
        A --- C[Child component B<br/>Have their own count]
    end
    
    subgraph "After the status upgrade(Data Sharing)"
        D[Parent Component App<br/>**count Here**] --- E[Child component A<br/>Read count, Call setCount]
        D --- F[Child component B<br/>Read count, Call setCount]
    end

▶ مثال: التطبيق العملي لتعزيز الحالة

JSX 📖 للعرض فقط
// ============================================
// Example:Temperature Converter(Status Upgrade)
// Features:Converting Between Celsius and Fahrenheit,Share the same temperature value
// ============================================

// ---- Child component:Celsius Temperature Input ----
function CelsiusInput({ celsius, onCelsiusChange }) {
  return (
    <div>
      <label>Celsius(°C):</label>
      <input
        value={celsius}
        onChange={e => onCelsiusChange(e.target.value)}
        style={{ margin: '8px', padding: '4px' }}
      />
    </div>
  )
}

// ---- Child component:Fahrenheit Temperature Input ----
function FahrenheitInput({ fahrenheit, onFahrenheitChange }) {
  return (
    <div>
      <label>Fahrenheit(°F):</label>
      <input
        value={fahrenheit}
        onChange={e => onFahrenheitChange(e.target.value)}
        style={{ margin: '8px', padding: '4px' }}
      />
    </div>
  )
}

// ---- Parent Component:Status is managed here ----
function TemperatureConverter() {
  // State Propagated to the Common Parent Component
  const [temperature, setTemperature] = useState('')

  function handleCelsiusChange(value) {
    setTemperature(value)  // Store at Celsius
    // There is no need for two states!Keep only one,Another one calculated using a formula
  }

  function handleFahrenheitChange(value) {
    // Fahrenheit → Celsius:°C = (°F - 32) × 5/9
    setTemperature(value ? ((parseFloat(value) - 32) * 5 / 9).toFixed(1) : '')
  }

  const celsius = temperature
  const fahrenheit = temperature
    ? (parseFloat(temperature) * 9 / 5 + 32).toFixed(1)
    : ''

  return (
    <div style={{ padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
      <h2>Temperature Converter</h2>
      <CelsiusInput celsius={celsius} onCelsiusChange={handleCelsiusChange} />
      <FahrenheitInput fahrenheit={fahrenheit} onFahrenheitChange={handleFahrenheitChange} />
      {temperature && (
        <p style={{ color: '#666', marginTop: '12px' }}>
          {celsius}°C = {fahrenheit}°F
        </p>
      )}
    </div>
  )
}
// Enter in the Celsius input field 100 → Automatic Display in Fahrenheit 212°F
// Enter in the Fahrenheit input field 212 → Automatic Celsius Display 100°C
49 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

7. مثال كامل: عداد سلة التسوق

JSX
// ============================================
// Complete Example:Shopping Cart(useState Comprehensive Application)
// Features:Add Item、Increase or Decrease Quantity、Delete、Total Price Calculation
// ============================================

import { useState } from 'react'

function ShoppingCart() {
  // Several State Variable,Logical Breakdown
  const [items, setItems] = useState([
    { id: 1, name: 'React Hands-On Tutorials', price: 89, qty: 1 },
    { id: 2, name: 'TypeScript Getting Started', price: 59, qty: 2 }
  ])
  const [discountCode, setDiscountCode] = useState('')
  const [appliedDiscount, setAppliedDiscount] = useState(0)

  // Increase the quantity(Functional updates ensure accuracy)
  function increment(id) {
    setItems(items.map(item =>
      item.id === id ? { ...item, qty: item.qty + 1 } : item
    ))
  }

  // Decrease the quantity(No less than 1)
  function decrement(id) {
    setItems(items.map(item =>
      item.id === id ? { ...item, qty: Math.max(1, item.qty - 1) } : item
    ))
  }

  // Delete Product
  function remove(id) {
    setItems(items.filter(item => item.id !== id))
  }

  // Apply a discount code
  function applyDiscount() {
    if (discountCode === 'REACT2026') {
      setAppliedDiscount(20)  // over 100 minus 20
    } else {
      alert('The discount code is invalid.')
    }
  }

  // Calculate the Total Price
  const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0)
  const total = Math.max(0, subtotal - appliedDiscount)

  return (
    <div style={{ maxWidth: '600px', margin: '0 auto' }}>
      <h2>🛒 Shopping Cart</h2>

      {/* Product List */}
      {items.length === 0 ? (
        <p style={{ color: '#999', textAlign: 'center', padding: '40px' }}>
          Your shopping cart is empty,Go check it out!!
        </p>
      ) : (
        items.map(item => (
          <div key={item.id} style={{
            display: 'flex', alignItems: 'center',
            padding: '12px', borderBottom: '1px solid #f0f0f0'
          }}>
            <div style={{ flex: 1 }}>
              <h4 style={{ margin: 0 }}>{item.name}</h4>
              <p style={{ margin: '4px 0', color: '#ff4d4f' }}>${item.price}</p>
            </div>
            
            {/* Quantity Control */}
            <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
              <button onClick={() => decrement(item.id)} style={btnStyle}>−</button>
              <span>{item.qty}</span>
              <button onClick={() => increment(item.id)} style={btnStyle}>+</button>
            </div>

            {/* Subtotal */}
            <p style={{ margin: '0 16px', fontWeight: 'bold', width: '80px', textAlign: 'right' }}>
              ${item.price * item.qty}
            </p>

            {/* Delete */}
            <button onClick={() => remove(item.id)} style={{ ...btnStyle, backgroundColor: '#ff4d4f', color: 'white' }}>
              ✕
            </button>
          </div>
        ))
      )}

      {/* Discount Code */}
      <div style={{ marginTop: '16px', display: 'flex', gap: '8px' }}>
        <input
          value={discountCode}
          onChange={e => setDiscountCode(e.target.value)}
          placeholder="Enter the discount code"
          style={{ flex: 1, padding: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }}
        />
        <button onClick={applyDiscount} style={{
          padding: '8px 16px', backgroundColor: '#52c41a', color: 'white',
          border: 'none', borderRadius: '4px', cursor: 'pointer'
        }}>
          Applications
        </button>
      </div>

      {/* Price Summary */}
      <div style={{ marginTop: '16px', padding: '16px', backgroundColor: '#fafafa', borderRadius: '8px' }}>
        <p>Subtotal:${subtotal}</p>
        {appliedDiscount > 0 && <p style={{ color: '#52c41a' }}>Discount: -${appliedDiscount}</p>}
        <p style={{ fontSize: '20px', fontWeight: 'bold' }}>Total:${total}</p>
      </div>
    </div>
  )
}

const btnStyle = {
  width: '32px', height: '32px',
  border: '1px solid #d9d9d9', borderRadius: '4px',
  backgroundColor: 'white', cursor: 'pointer',
  fontSize: '16px', display: 'flex', alignItems: 'center',
  justifyContent: 'center'
}

مسار التفاعل:

  • اضغط على +/− لتعديل الكمية؛ ويتم تحديث المجموع الفرعي والمجموع الكلي في الوقت الفعلي
  • انقر على ✕ لحذف عنصر؛ اعرض رسالة توضيحية سهلة الفهم عندما تكون سلة التسوق فارغة
  • أدخل الرمز الترويجي REACT2026 لتوفير 20 يوان

▶ المثال 3: تحديث حالة كائن — تعديل معلومات المستخدم

JSX
function UserEditor() {
  const [user, setUser] = useState({ name: 'Alice', email: 'alice@test.com', age: 28 })

  function updateField(field, value) {
    setUser(prev => ({ ...prev, [field]: value }))
  }

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>Edit Profile</h3>
      <input value={user.name} onChange={e => updateField('name', e.target.value)}
        placeholder="Name" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <input value={user.email} onChange={e => updateField('email', e.target.value)}
        placeholder="Email" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <input type="number" value={user.age} onChange={e => updateField('age', Number(e.target.value))}
        placeholder="Age" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4, fontSize: 13 }}>
        {JSON.stringify(user, null, 2)}
      </pre>
    </div>
  )
}
▶ جرّب الكود

▶ المثال 4: حالات الكائنات المتداخلة — إدارة العناوين

JSX
function AddressForm() {
  const [address, setAddress] = useState({
    street: '', city: '', zip: '',
    country: 'US', isPrimary: true,
  })

  function update(path, value) {
    setAddress(prev => ({ ...prev, [path]: value }))
  }

  return (
    <div style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>Shipping Address</h3>
      <input value={address.street} onChange={e => update('street', e.target.value)}
        placeholder="Street" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
      <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
        <input value={address.city} onChange={e => update('city', e.target.value)}
          placeholder="City" style={{ flex: 2, padding: 8, borderRadius: 4 }} />
        <input value={address.zip} onChange={e => update('zip', e.target.value)}
          placeholder="ZIP" style={{ flex: 1, padding: 8, borderRadius: 4 }} />
      </div>
      <select value={address.country} onChange={e => update('country', e.target.value)}
        style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }}>
        <option value="US">United States</option>
        <option value="CN">China</option>
        <option value="JP">Japan</option>
      </select>
      <label>
        <input type="checkbox" checked={address.isPrimary}
          onChange={e => update('isPrimary', e.target.checked)} /> Primary address
      </label>
    </div>
  )
}
▶ جرّب الكود

▶ المثال 5: التحديثات الوظيفية تحل مشكلة التحديثات المجمعة

JSX
function ScoreBoard() {
  const [score, setScore] = useState(0)
  const [multiplier, setMultiplier] = useState(1)

  function addPoints(base) {
    setScore(prev => prev + base * multiplier)
  }

  function resetScore() {
    setScore(0)
    setMultiplier(1)
  }

  function doubleMultiplier() {
    setMultiplier(prev => Math.min(prev * 2, 8))
  }

  return (
    <div style={{ maxWidth: 300, margin: '0 auto', textAlign: 'center' }}>
      <h3>Score: {score}</h3>
      <p>Multiplier: x{multiplier}</p>
      <div style={{ display: 'flex', gap: 8, justifyContent: 'center', marginBottom: 8 }}>
        <button onClick={() => addPoints(10)} style={{ padding: '8px 16px', cursor: 'pointer' }}>+10 pts</button>
        <button onClick={() => addPoints(50)} style={{ padding: '8px 16px', cursor: 'pointer' }}>+50 pts</button>
        <button onClick={() => addPoints(100)} style={{ padding: '8px 16px', cursor: 'pointer' }}>+100 pts</button>
      </div>
      <div style={{ display: 'flex', gap: 8, justifyContent: 'center' }}>
        <button onClick={doubleMultiplier} style={{ padding: '8px 16px', cursor: 'pointer' }}>2x Multiplier</button>
        <button onClick={resetScore} style={{ padding: '8px 16px', cursor: 'pointer' }}>Reset</button>
      </div>
    </div>
  )
}
▶ جرّب الكود

❓ أسئلة شائعة

س ما الفرق في الأداء بين useState والمتغيرات العادية؟
ج تتسبب useState في عبء إضافي (تتبع التغييرات، وجدولة العرض)، لذا يجب استخدامها فقط في الحالات التي «يتم فيها تشغيل تحديثات واجهة المستخدم عند حدوث تغييرات». إذا تغيرت المتغير ولكنها لا تتطلب تحديثًا لواجهة المستخدم (مثل معرّف المؤقت أو موضع التمرير)، فإن استخدام useRef (الدرس 10) يكون أكثر ملاءمة.
س كم عدد useStates التي يمكن أن يتضمنها مكون واحد؟ وهل توجد أي مشكلات في الأداء؟
ج لا يوجد حد أقصى لعددها. توصي الوثائق الرسمية لـ React بتقسيم العناصر إلى عدة useStates بناءً على المنطق بدلاً من استخدام كائن واحد كبير. كل استدعاء لـ useState يمثل «وحدة حالة»، ويمكن لـ React التعامل بكفاءة مع عدد كبير من useStates. عادةً ما تحتوي المكونات الشائعة على 3–8 useStates.
س هل يتم تحديث setState بشكل متزامن أم غير متزامن؟
ج قبل الإصدار React 18، كان setState يُجمع تلقائيًا ضمن معالجات الأحداث، ولكنه كان متزامنًا ضمن استدعاءات setTimeout وPromise. أدخلت React 18 ميزة التجميع التلقائي، لذا أصبحت setState الآن تُعالج بشكل غير متزامن على دفعات بغض النظر عما إذا تم استدعاؤها داخل معالج أحداث أو setTimeout أو Promise أو استدعاء رد فعل fetch. إذا كنت بحاجة ماسة للوصول إلى DOM المحدث بشكل متزامن، فيمكنك استخدام flushSync(() => setState(...)).

📖 ملخص


📝 تمارين

  1. تمرين أساسي (مستوى الصعوبة ⭐): أنشئ مكونًا باسم LikeButton يتبدل بين الحالتين ❤️ و🤍 عند النقر عليه ويعرض عدد الإعجابات.
  2. تمرين متقدم (مستوى الصعوبة ⭐⭐): أنشئ مكونًا ExpenseTracker يستخدم useState لإدارة قائمة بالمصروفات (الوصف، المبلغ، التاريخ)، ويدعم إضافة العناصر وحذفها.
  3. التحدي (الصعوبة: ⭐⭐⭐): قم بإنشاء مكون ColorPicker يستخدم ثلاث حالات من useState لإدارة قيم أشرطة التمرير R و G و B (0–255)، ويعرض معاينة اللون في الوقت الفعلي ورمز اللون السداسي العشري.
Web-Tutorial.com

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

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

100%