مقدمة في إدارة الحالة والتعرف على هوك useState
| المفهوم الهندسي | الآلية البرمجية في React | الفائدة والتطبيق المباشر |
|---|---|---|
| State | ذاكرة المكون الداخلية التي تحتفظ بالبيانات التفاعلية عبر عمليات الرندرة | تمكين الواجهة من تتبع التغييرات مثل عدد سلة المشتريات أو حالة إظهار القائمة |
| useState | الدالة الأساسية لإعلان متغير حالة ودالة لتحديثه [state، setState] | إعلام ريأكت بتغير البيانات لتحديد الأجزاء المتأثرة وتحديث الـ DOM تلقائياً |
| Immutability | عدم تعديل قيم متغير الحالة مباشرة بل عبر دالة التحديث حصراً | ضمان عمل خوارزمية Diffing وتتبع الشجرة بدون أخطاء خفية |
| Functional Updates | تمرير دالة تحديث تاخذ القيمة القديمة setState(prev => prev + 1) | تجنب مشاكل المزامنة والتحديثات المتزامنة المتعددة |
في المتغيرات العادية داخل جافا سكريبت، عند تغير قيمة متغير مثل let count = 0; count++، تتغير القيمة في الذاكرة ولكن الواجهة لا تشعر بهذا التغير ولا تعيد رسم نفسها.
هنا يأتي دور الـ State (الحالة). الـ State هي الذاكرة الخاصة بالمكون. عندما تتغير قيمة الـ State، تفهم ريأكت فوراً أن الواجهة بحاجة إلى تحديث، فتقوم بتنفيذ المكون مجدداً (Re-render) ليعكس القيمة الجديدة على الشاشة بسلاسة.
الـ Hooks في React هي دوال خاصة تبدأ كلمتها دائماً بـ use تسمح لك بـ "الربط" بمزايا ريأكت الداخلية من داخل المكونات الوظيفية (Functional Components). دالة useState ترجع مصفوفة تحتوي على عنصرين بدقة عبر الـ Array Destructuring:
| 1 | const [state, setState] = useState(initialValue); |
| 1 | import { useState } from 'react'; |
| 2 | |
| 3 | function StudentDashboard() { |
| 4 | // 1. تعريف حالة لعدد الساعات المكتملة (القيمة الابتدائية: 0) |
| 5 | const [studyHours, setStudyHours] = useState(0); |
| 6 | |
| 7 | // 2. تعريف حالة لتبديل وضع الشاشة (القيمة الابتدائية: false) |
| 8 | const [isDarkMode, setIsDarkMode] = useState(false); |
| 9 | |
| 10 | // دالة زيادة الساعات باستخدام النمط الوظيفي الآمن |
| 11 | const handleAddHour = () => { |
| 12 | setStudyHours((prevHours) => prevHours + 1); |
| 13 | }; |
| 14 | |
| 15 | // دالة إنقاص الساعات مع ضمان عدم الهبوط تحت الصفر |
| 16 | const handleRemoveHour = () => { |
| 17 | setStudyHours((prevHours) => (prevHours > 0 ? prevHours - 1 : 0)); |
| 18 | }; |
| 19 | |
| 20 | // دالة تبديل الثيم |
| 21 | const toggleTheme = () => { |
| 22 | setIsDarkMode((prevTheme) => !prevTheme); |
| 23 | }; |
| 24 | |
| 25 | // تنسيقات ديناميكية بناءً على حالة الثيم |
| 26 | const cardStyle = { |
| 27 | padding: '20px', |
| 28 | borderRadius: '8px', |
| 29 | backgroundColor: isDarkMode ? '#1e1e2f' : '#f5f5f5', |
| 30 | color: isDarkMode ? '#ffffff' : '#333333', |
| 31 | border: '2px solid #673ab7', |
| 32 | fontFamily: 'sans-serif' |
| 33 | }; |
| 34 | |
| 35 | return ( |
| 36 | <div style={cardStyle}> |
| 37 | <h2>لوحة إحصائيات الطالب - منصة نوفيل</h2> |
| 38 | |
| 39 | <button |
| 40 | onClick={toggleTheme} |
| 41 | style={{ padding: '8px 12px', marginBottom: '15px', cursor: 'pointer' }} |
| 42 | > |
| 43 | التحويل إلى الوضع {isDarkMode ? 'النهاري ☀️' : 'الليلي 🌙'} |
| 44 | </button> |
| 45 | |
| 46 | <hr style={{ borderColor: '#673ab7' }} /> |
| 47 | |
| 48 | <h3>إجمالي ساعات المذاكرة اليومية: {studyHours} ساعة</h3> |
| 49 | |
| 50 | <div style={{ display: 'flex', gap: '10px' }}> |
| 51 | <button |
| 52 | onClick={handleAddHour} |
| 53 | style={{ backgroundColor: '#4caf50', color: '#fff', border: 'none', padding: '10px 15px', borderRadius: '4px', cursor: 'pointer' }} |
| 54 | > |
| 55 | إضافة ساعة + |
| 56 | </button> |
| 57 | |
| 58 | <button |
| 59 | onClick={handleRemoveHour} |
| 60 | style={{ backgroundColor: '#f44336', color: '#fff', border: 'none', padding: '10px 15px', borderRadius: '4px', cursor: 'pointer' }} |
| 61 | > |
| 62 | إنقاص ساعة - |
| 63 | </button> |
| 64 | </div> |
| 65 | </div> |
| 66 | ); |
| 67 | } |
| 68 | |
| 69 | export default StudentDashboard; |
تركاية هذا الدرس الهندسية هي فهم آلية التحديث الدفعي (Batching) وعدم تعديل الحالة بناءً على قيمتها مباشرة إذا كنت تجري تحديثات متتالية! لو كتبت هذا الكود لتعديل العداد مرتين متتاليتين:
| 1 | // ❌ كود خاطئ يسبب نتائج غير متوقعة |
| 2 | const handleDoubleAdd = () => { |
| 3 | setStudyHours(studyHours + 1); |
| 4 | setStudyHours(studyHours + 1); |
| 5 | }; |
ستفاجأ أن العداد زاد بمقدار 1 فقط وليس 2! لأن studyHours في لحظة استدعاء الدالة تكون قيمتها القديمة ثابتة في الممر الحالي (Closure). الحل الهندسي الصحيح: التحديث الوظيفي (Functional Update) بتمرير دالة تأخذ القيمة الأحدث مباشرة من الـ Queue الخاص بريأكت:
| 1 | // ✅ كود احترافي يضمن التحديث المتسلسل بدقة |
| 2 | const handleDoubleAdd = () => { |
| 3 | setStudyHours((prev) => prev + 1); |
| 4 | setStudyHours((prev) => prev + 1); |
| 5 | }; |