React: أساسيات State: useState
آخر تحديث: 2026-08-26
الحالة (State) هي ذاكرة المكون. إذا كانت الخصائص (Props) هي معلمات تُعطى لك من قبل عناصر أخرى (للقراءة فقط)، فإن الحالة (State) هي دفتر ملاحظاتك الصغير الخاص (القابل للتعديل). أما useState فهي «رقاقة الذاكرة» التي يثبتها React في المكون.
1. ما ستتعلمه
- ما هي «الدولة»؟ ولماذا نحتاج إليها؟
- الاستخدام الأساسي لـ useState
- الطريقة الصحيحة لتحديث حالات المصفوفات والكائنات (الثبات)
- إدارة متغيرات الحالة المتعددة
- الارتقاء بمستوى الدولة
2. تأملات أثارها عداد
(1) المشكلة: المتغيرات العادية لا تؤدي إلى تحديث واجهة المستخدم
يريد بوب إنشاء عداد بسيط: عندما تنقر على الزر، يزداد الرقم بمقدار 1.
// ❌ 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
// ✅ 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) تحليل المهمة
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) آلية التحديث
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) تحديثات وظيفية
إذا كانت الحالة الجديدة تعتمد على الحالة القديمة، فيجب عليك استخدام التحديثات الوظيفية:
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) العناصر المطلوب تحديثها
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) تحديث المصفوفة
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>
)
}
▶ مثال: جدول مرجعي سريع لعمليات المصفوفات
// ============================================
// 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. متغيرات الحالة المتعددة
يمكن أن يحتوي المكون على متغيرات حالة متعددة. يُنصح بتقسيمها وفقًا للمنطق بدلاً من حشرها في كائن واحد كبير:
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. النهوض بالدولة
عندما تحتاج عدة مكونات إلى مشاركة الحالة نفسها، قم بنقل الحالة إلى أقرب مكون أب مشترك بينها.
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
▶ مثال: التطبيق العملي لتعزيز الحالة
// ============================================
// 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
7. مثال كامل: عداد سلة التسوق
// ============================================
// 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: تحديث حالة كائن — تعديل معلومات المستخدم
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: حالات الكائنات المتداخلة — إدارة العناوين
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: التحديثات الوظيفية تحل مشكلة التحديثات المجمعة
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 التي يمكن أن يتضمنها مكون واحد؟ وهل توجد أي مشكلات في الأداء؟useStates بناءً على المنطق بدلاً من استخدام كائن واحد كبير. كل استدعاء لـ useState يمثل «وحدة حالة»، ويمكن لـ React التعامل بكفاءة مع عدد كبير من useStates. عادةً ما تحتوي المكونات الشائعة على 3–8 useStates.setState بشكل متزامن أم غير متزامن؟setState يُجمع تلقائيًا ضمن معالجات الأحداث، ولكنه كان متزامنًا ضمن استدعاءات setTimeout وPromise. أدخلت React 18 ميزة التجميع التلقائي، لذا أصبحت setState الآن تُعالج بشكل غير متزامن على دفعات بغض النظر عما إذا تم استدعاؤها داخل معالج أحداث أو setTimeout أو Promise أو استدعاء رد فعل fetch. إذا كنت بحاجة ماسة للوصول إلى DOM المحدث بشكل متزامن، فيمكنك استخدام flushSync(() => setState(...)).📖 ملخص
- «الحالة» هي «ذاكرة» المكون؛ وعندما تتغير، فإنها تؤدي تلقائيًا إلى تحديث واجهة المستخدم
useState(initialValue)تُرجع المصفوفة[value, setValue]- الثبات: لا تقم أبدًا بتعديل الحالة مباشرةً؛ بل قم بدلاً من ذلك بإنشاء كائن أو مصفوفة جديدة لتحل محلها.
- التحديث الوظيفي
setCount(prev => prev + 1)يكون أكثر دقة عند الاعتماد على القيم القديمة - رفع الحالة: عندما تحتاج عدة مكونات فرعية إلى مشاركة البيانات، يتم رفع الحالة إلى مكون أب مشترك.
📝 تمارين
- تمرين أساسي (مستوى الصعوبة ⭐): أنشئ مكونًا باسم
LikeButtonيتبدل بين الحالتين ❤️ و🤍 عند النقر عليه ويعرض عدد الإعجابات. - تمرين متقدم (مستوى الصعوبة ⭐⭐): أنشئ مكونًا
ExpenseTrackerيستخدمuseStateلإدارة قائمة بالمصروفات (الوصف، المبلغ، التاريخ)، ويدعم إضافة العناصر وحذفها. - التحدي (الصعوبة: ⭐⭐⭐): قم بإنشاء مكون
ColorPickerيستخدم ثلاث حالات منuseStateلإدارة قيم أشرطة التمرير R و G و B (0–255)، ويعرض معاينة اللون في الوقت الفعلي ورمز اللون السداسي العشري.