توثيق React 19 الشامل

دليل مفصل وشامل لجميع ميزات ومفاهيم React 19 مع شرح وأمثلة عملية.

مقدمة إلى React 19

React 19 هو أحدث إصدار من مكتبة React لبناء واجهات المستخدم. يجلب هذا الإصدار تحسينات كبيرة في الأداء وتجربة المطور، مع التركيز على تعزيز قدرات مكونات الخادم والعميل.

لماذا React 19؟

أداء محسن

يقدم React 19 تحسينات كبيرة في الأداء من خلال محرك عرض جديد ونظام تخزين مؤقت ذكي، مما يؤدي إلى تجربة مستخدم أكثر سلاسة.

واجهة برمجة تطبيقات أفضل

يتضمن React 19 واجهة برمجة تطبيقات محسنة تجعل كتابة التعليمات البرمجية أكثر بديهية وأسهل في الفهم للمطورين الجدد.

مكونات الخادم

يحسن React 19 بشكل كبير تجربة مكونات الخادم، مما يسمح بعرض المحتوى على الخادم للتحميل السريع مع الحفاظ على التفاعلية.

نظام متكامل

يوفر React 19 نظامًا أكثر تكاملاً، مما يقلل من الحاجة إلى مكتبات خارجية ويبسط عملية التطوير.

الفلسفة الأساسية

يستمر React 19 في اتباع فلسفة "Learn Once, Write Anywhere" (تعلم مرة، اكتب في أي مكان)، مع التركيز على:

  • التصريحية: وصف ما يجب أن تبدو عليه واجهة المستخدم بدلاً من كيفية تحقيق ذلك.
  • القائمة على المكونات: بناء واجهات معزولة وقابلة لإعادة الاستخدام.
  • تعلم واحد، كتابة في أي مكان: يمكن استخدام المهارات نفسها في تطوير الويب والجوال.

التثبيت والإعداد

يمكن البدء باستخدام React 19 بطرق متعددة، اعتمادًا على احتياجات مشروعك. فيما يلي أكثر الطرق شيوعًا:

إنشاء تطبيق جديد

الطريقة الموصى بها لإنشاء تطبيق React 19 جديد هي استخدام create-react-app مع قالب React 19:

npx create-react-app my-app --template react-19
cd my-app
npm start

استخدام Vite

يوفر Vite بيئة تطوير أسرع ويمكن استخدامه مع React 19:

npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm install react@latest react-dom@latest
npm run dev

التثبيت في مشروع موجود

لتحديث مشروع React موجود إلى الإصدار 19:

npm install react@latest react-dom@latest

متطلبات النظام

  • Node.js الإصدار 18.0.0 أو أحدث
  • npm الإصدار 8.0.0 أو أحدث
  • متصفح حديث يدعم ES6 (Chrome، Firefox، Safari، Edge الجديد)

JSX في React 19

JSX هو امتداد بناء الجملة لـ JavaScript الذي يسمح بكتابة شيفرة تشبه HTML ضمن ملفات JavaScript. في React 19، تم تحسين دعم JSX مع تبسيط القواعد والتعامل مع الحالات الخاصة.

أساسيات JSX

// مثال بسيط لـ JSX
function Greeting() {
  const name = 'أحمد';
  return (
    <div className="greeting">
      <h1>مرحباً، {name}!</h1>
      <p>يسعدنا وجودك معنا.</p>
    </div>
  );
}

في المثال أعلاه، يمكنك ملاحظة:

  • استخدام الأقواس المنحنية {} لتضمين تعبيرات JavaScript
  • استخدام className بدلاً من class (لأن class هي كلمة محجوزة في JavaScript)
  • البنية التي تشبه HTML ولكنها تُترجم إلى استدعاءات React.createElement()

الشروط في JSX

function ConditionalGreeting({ isLoggedIn, username }) {
  return (
    <div>
      {isLoggedIn ? (
        <h1>مرحباً، {username}!</h1>
      ) : (
        <h1>مرحباً، زائرنا العزيز!</h1>
      )}
    </div>
  );
}

القوائم في JSX

function ItemList({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

ملاحظة مهمة: دائمًا استخدم خاصية key فريدة عند عرض قوائم في React لمساعدة خوارزمية التوفيق.

الأحداث في JSX

function ClickButton() {
  const handleClick = () => {
    alert('تم النقر على الزر!');
  };

  return (
    <button onClick={handleClick}>
      انقر هنا
    </button>
  );
}

تحسينات JSX في React 19

  • دعم أفضل للأجزاء (Fragments) بدون الحاجة لاستيراد صريح
  • معالجة محسنة للعناصر المتداخلة
  • التعامل مع الشروط المعقدة بشكل أكثر كفاءة

المكونات في React 19

المكونات هي اللبنات الأساسية لأي تطبيق React. في الإصدار 19، تم تعزيز نظام المكونات مع التركيز على المكونات الوظيفية وتقليل الحاجة إلى مكونات الفئات.

المكونات الوظيفية

المكونات الوظيفية هي وظائف JavaScript بسيطة تقبل props كمُدخلات وتُرجع عناصر React:

// مكون وظيفي بسيط
function Welcome(props) {
  return <h1>مرحباً، {props.name}</h1>;
}

// استخدام السهم الدالّي (arrow function)
const Welcome = (props) => {
  return <h1>مرحباً، {props.name}</h1>;
};

// استخدام مبسط مع السهم الدالّي
const Welcome = (props) => <h1>مرحباً، {props.name}</h1>;

مكونات الفئات

على الرغم من أن المكونات الوظيفية هي المفضلة في React 19، إلا أن مكونات الفئات لا تزال مدعومة للتوافق مع الإصدارات السابقة:

import React from 'react';

class Welcome extends React.Component {
  render() {
    return <h1>مرحباً، {this.props.name}</h1>;
  }
}

Props - الخصائص

Props هي البيانات التي تمرر من مكون أب إلى مكون ابن، وهي للقراءة فقط (غير قابلة للتعديل):

// تعريف المكون
function UserProfile(props) {
  return (
    <div>
      <h2>{props.name}</h2>
      <p>العمر: {props.age}</p>
      <p>البريد الإلكتروني: {props.email}</p>
    </div>
  );
}

// استخدام المكون
function App() {
  return (
    <div>
      <UserProfile 
        name="سارة أحمد" 
        age={28} 
        email="[email protected]" 
      />
    </div>
  );
}

تحطيم Props (Destructuring)

// استخدام تحطيم Props
function UserProfile({ name, age, email }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>العمر: {age}</p>
      <p>البريد الإلكتروني: {email}</p>
    </div>
  );
}

القيم الافتراضية للـ Props

// تعيين قيم افتراضية للـ Props
function UserProfile({ name, age = 25, email = 'غير متوفر' }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>العمر: {age}</p>
      <p>البريد الإلكتروني: {email}</p>
    </div>
  );
}

// أو باستخدام defaultProps
UserProfile.defaultProps = {
  age: 25,
  email: 'غير متوفر'
};

Children - العناصر الفرعية

يمكن تمرير المحتوى بين علامات فتح وإغلاق المكون ويمكن الوصول إليه من خلال props.children:

// مكون يستخدم children
function Card({ title, children }) {
  return (
    <div className="card">
      <h3 className="card-title">{title}</h3>
      <div className="card-content">
        {children}
      </div>
    </div>
  );
}

// استخدام المكون
function App() {
  return (
    <Card title="معلومات هامة">
      <p>هذا محتوى البطاقة الذي سيظهر داخل Card-content.</p>
      <button>اتصل بنا</button>
    </Card>
  );
}

الخطافات (Hooks) في React 19

الخطافات في React تسمح لك باستخدام حالة ومميزات React الأخرى بدون كتابة مكونات فئة. في React 19، تمت إضافة خطافات جديدة وتحسين الموجودة.

useState

يسمح لك بإضافة حالة محلية إلى المكونات الوظيفية:

import { useState } from 'react';

function Counter() {
  // تعريف متغير حالة 'count' مع قيمة أولية 0
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>لقد نقرت {count} مرات</p>
      <button onClick={() => setCount(count + 1)}>
        زيادة العدد
      </button>
    </div>
  );
}

useEffect

يسمح لك بتنفيذ "التأثيرات الجانبية" في المكونات الوظيفية (مثل جلب البيانات أو الاشتراك في الأحداث):

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // سيتم تنفيذ هذا عند تركيب المكون لأول مرة وعند تغيير userId
    setLoading(true);
    
    fetch(`https://api.example.com/users/${userId}`)
      .then(response => response.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      })
      .catch(error => {
        console.error('Error fetching user:', error);
        setLoading(false);
      });
      
    return () => {
      // التنظيف - سيتم تنفيذه عند إزالة المكون أو قبل تنفيذ التأثير التالي
      console.log('تنظيف التأثير');
    };
  }, [userId]); // مصفوفة التبعيات - سيتم تنفيذ التأثير فقط عند تغيير userId
  
  if (loading) return <div>جاري التحميل...</div>;
  if (!user) return <div>لم يتم العثور على المستخدم</div>;
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

useContext

يسمح لك بالوصول إلى قيم السياق (Context) بدون استخدام مكونات المستهلك:

import { createContext, useContext, useState } from 'react';

// إنشاء سياق
const ThemeContext = createContext('light');

// مكون الموفر
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// مكون يستخدم السياق
function ThemedButton() {
  // استخدام السياق
  const { theme, toggleTheme } = useContext(ThemeContext);
  
  return (
    <button
      onClick={toggleTheme}
      style={{
        backgroundColor: theme === 'light' ? '#ffffff' : '#333333',
        color: theme === 'light' ? '#333333' : '#ffffff',
        padding: '10px 20px',
        border: 'none',
        borderRadius: '5px'
      }}
    >
      تبديل السمة ({theme})
    </button>
  );
}

// استخدام المكونات
function App() {
  return (
    <ThemeProvider>
      <div>
        <h1>مثال على useContext</h1>
        <ThemedButton />
      </div>
    </ThemeProvider>
  );
}

useReducer

بديل لـ useState عندما تكون لديك منطق حالة معقد يتضمن عدة قيم:

import { useReducer } from 'react';

// تعريف المُختزل (reducer)
function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { id: Date.now(), text: action.text, completed: false }];
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      );
    case 'DELETE_TODO':
      return state.filter(todo => todo.id !== action.id);
    default:
      return state;
  }
}

function TodoApp() {
  // استخدام useReducer مع حالة أولية كمصفوفة فارغة
  const [todos, dispatch] = useReducer(todoReducer, []);
  const [text, setText] = useState('');
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (!text.trim()) return;
    dispatch({ type: 'ADD_TODO', text });
    setText('');
  };
  
  return (
    <div>
      <h2>قائمة المهام</h2>
      
      <form onSubmit={handleSubmit}>
        <input
          value={text}
          onChange={(e) => setText(e.target.value)}
          placeholder="أضف مهمة جديدة..."
        />
        <button type="submit">إضافة</button>
      </form>
      
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <span
              style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
              onClick={() => dispatch({ type: 'TOGGLE_TODO', id: todo.id })}
            >
              {todo.text}
            </span>
            <button onClick={() => dispatch({ type: 'DELETE_TODO', id: todo.id })}>
              حذف
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

الخطافات المخصصة

يمكنك إنشاء خطافات مخصصة لاستخراج منطق المكون إلى وظائف قابلة لإعادة الاستخدام:

import { useState, useEffect } from 'react';

// خطاف مخصص لجلب البيانات
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // إعادة ضبط الحالة في كل مرة يتغير فيها الـ URL
    setLoading(true);
    setData(null);
    setError(null);
    
    fetch(url)
      .then(response => {
        if (!response.ok) {
          throw new Error('فشل في جلب البيانات');
        }
        return response.json();
      })
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(error => {
        setError(error.message);
        setLoading(false);
      });
  }, [url]);

  return { data, loading, error };
}

// استخدام الخطاف المخصص
function UserList() {
  const { data, loading, error } = useFetch('https://api.example.com/users');

  if (loading) return <div>جاري التحميل...</div>;
  if (error) return <div>حدث خطأ: {error}</div>;
  if (!data) return <div>لا توجد بيانات</div>;

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

خطافات جديدة في React 19

يقدم React 19 عدة خطافات جديدة لتحسين تجربة المطور:

  • useFormStatus: للوصول إلى حالة نماذج HTML بسهولة
  • useOptimistic: لتنفيذ تحديثات متفائلة للواجهة قبل اكتمال العمليات الخلفية
  • useTransition: لتحسين تجربة المستخدم أثناء التحميل
  • useDeferredValue: لتأجيل تحديث قيم معينة لتحسين الأداء

مكونات الخادم في React 19

تعد مكونات الخادم (Server Components) من أهم ميزات React 19، حيث تمكّن المطورين من إنشاء تطبيقات أسرع وأكثر كفاءة من خلال تقسيم العمل بين الخادم والعميل.

ما هي مكونات الخادم؟

مكونات الخادم هي مكونات React يتم تشغيلها وعرضها على الخادم فقط. يتم إرسال نتائج العرض إلى المتصفح بدلاً من الكود نفسه، مما يؤدي إلى:

  • تقليل حجم حزمة JavaScript التي يتم تحميلها إلى المتصفح
  • تحسين أداء التحميل الأولي للصفحة
  • إمكانية الوصول المباشر إلى موارد الخادم (قواعد البيانات، نظام الملفات، الخ)
  • تحسين أمان التطبيق من خلال إبقاء البيانات الحساسة على الخادم

مكونات العميل مقابل مكونات الخادم

مكونات الخادم

  • تعمل على الخادم فقط
  • لا يمكنها استخدام الخطافات التفاعلية مثل useState
  • يمكنها الوصول مباشرة إلى موارد الخادم
  • لا تضيف حجمًا إلى حزمة JavaScript للعميل
  • لا يمكنها الاستجابة للأحداث من المستخدم

مكونات العميل

  • تعمل في المتصفح
  • يمكنها استخدام جميع خطافات React
  • يمكنها الاستجابة لأحداث المستخدم
  • تزيد من حجم حزمة JavaScript
  • لا يمكنها الوصول مباشرة إلى موارد الخادم

تطبيق مكونات الخادم

لتمييز مكون العميل في React 19، يتم استخدام توجيه "use client" في بداية الملف:

// مكون خادم (افتراضي)
// لا يحتاج إلى توجيه خاص
async function ServerComponent() {
  // يمكننا استخدام async/await هنا
  const data = await fetch('https://api.example.com/data').then(r => r.json());
  
  return (
    <div>
      <h1>بيانات من الخادم</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

// في ملف منفصل
"use client"
// هذا توجيه يحدد أن هذا مكون عميل
import { useState } from 'react';

function ClientComponent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <h1>مكون تفاعلي</h1>
      <p>العدد: {count}</p>
      <button onClick={() => setCount(count + 1)}>زيادة</button>
    </div>
  );
}

دمج مكونات الخادم والعميل

يمكن أن تستدعي مكونات الخادم مكونات العميل، ولكن العكس ليس صحيحًا بشكل مباشر:

// ServerPage.js - مكون خادم
import ClientCounter from './ClientCounter';
import { fetchUserData } from './database';

export default async function ServerPage() {
  // جلب البيانات مباشرة من قاعدة البيانات على الخادم
  const userData = await fetchUserData();
  
  return (
    <div>
      <h1>صفحة المستخدم</h1>
      <div>
        <h2>معلومات المستخدم (خادم)</h2>
        <p>الاسم: {userData.name}</p>
        <p>البريد الإلكتروني: {userData.email}</p>
      </div>
      
      {/* يمكننا استدعاء مكون عميل من مكون خادم */}
      <ClientCounter initialCount={userData.activityCount} />
    </div>
  );
}

// ClientCounter.js - مكون عميل
"use client"
import { useState } from 'react';

export default function ClientCounter({ initialCount }) {
  const [count, setCount] = useState(initialCount);
  
  return (
    <div>
      <h2>عداد النشاط (عميل)</h2>
      <p>العدد: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        تسجيل نشاط جديد
      </button>
    </div>
  );
}

تمرير البيانات بين مكونات الخادم والعميل

عند تمرير البيانات من مكونات الخادم إلى مكونات العميل، يتم تسلسل (serialize) هذه البيانات تلقائيًا. تأكد من أن جميع البيانات التي تمررها قابلة للتسلسل (مثل الأرقام والنصوص والكائنات البسيطة).

إدارة الحالة في React 19

توفر React 19 العديد من الخيارات لإدارة حالة التطبيق، بدءًا من الحلول البسيطة مثل useState وحتى حلول إدارة الحالة الشاملة للتطبيقات الكبيرة.

مستويات إدارة الحالة

حالة محلية

حالة داخل مكون واحد، مناسبة للبيانات التي تنتمي إلى مكون واحد فقط.

مثال: useState, useReducer

حالة المكونات المشتركة

حالة مشتركة بين عدة مكونات، غالبًا ما تكون متداخلة.

مثال: رفع الحالة، Context API

حالة التطبيق الشاملة

حالة على مستوى التطبيق بأكمله، مستخدمة عبر مكونات غير مرتبطة.

مثال: Context API + useReducer، مكتبات خارجية

Context API

توفر Context API طريقة لتمرير البيانات عبر شجرة المكونات دون الحاجة إلى تمرير الـ props يدويًا في كل مستوى:

import { createContext, useContext, useState } from 'react';

// 1. إنشاء السياق
const ShoppingCartContext = createContext();

// 2. إنشاء مزود السياق
function ShoppingCartProvider({ children }) {
  const [items, setItems] = useState([]);
  
  const addItem = (item) => {
    setItems(prevItems => [...prevItems, item]);
  };
  
  const removeItem = (itemId) => {
    setItems(prevItems => prevItems.filter(item => item.id !== itemId));
  };
  
  return (
    <ShoppingCartContext.Provider value={{ items, addItem, removeItem }}>
      {children}
    </ShoppingCartContext.Provider>
  );
}

// 3. إنشاء خطاف مخصص لاستخدام السياق
function useShoppingCart() {
  const context = useContext(ShoppingCartContext);
  if (!context) {
    throw new Error('useShoppingCart يجب استخدامه داخل ShoppingCartProvider');
  }
  return context;
}

// 4. استخدام السياق في المكونات
function ProductList() {
  const products = [
    { id: 1, name: 'هاتف ذكي', price: 999 },
    { id: 2, name: 'حاسوب محمول', price: 1299 },
    { id: 3, name: 'سماعات لاسلكية', price: 199 }
  ];
  
  const { addItem } = useShoppingCart();
  
  return (
    <div>
      <h2>المنتجات المتاحة</h2>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            {product.name} - {product.price} ريال
            <button onClick={() => addItem(product)}>
              إضافة للسلة
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

function ShoppingCart() {
  const { items, removeItem } = useShoppingCart();
  
  const total = items.reduce((sum, item) => sum + item.price, 0);
  
  return (
    <div>
      <h2>سلة التسوق</h2>
      {items.length === 0 ? (
        <p>السلة فارغة</p>
      ) : (
        <>
          <ul>
            {items.map(item => (
              <li key={item.id}>
                {item.name} - {item.price} ريال
                <button onClick={() => removeItem(item.id)}>
                  إزالة
                </button>
              </li>
            ))}
          </ul>
          <p>المجموع: {total} ريال</p>
        </>
      )}
    </div>
  );
}

// 5. استخدام المزود في التطبيق الرئيسي
function App() {
  return (
    <ShoppingCartProvider>
      <div>
        <h1>متجر إلكتروني</h1>
        <ProductList />
        <ShoppingCart />
      </div>
    </ShoppingCartProvider>
  );
}

إدارة الحالة المعقدة

للتطبيقات الأكبر والأكثر تعقيدًا، يمكنك دمج Context API مع useReducer:

import { createContext, useReducer, useContext } from 'react';

// التعريف الأولي للحالة
const initialState = {
  user: null,
  products: [],
  cart: [],
  isLoading: false,
  error: null
};

// تعريف المختزل
function appReducer(state, action) {
  switch (action.type) {
    case 'SET_USER':
      return { ...state, user: action.payload };
    case 'SET_PRODUCTS':
      return { ...state, products: action.payload };
    case 'ADD_TO_CART':
      return { ...state, cart: [...state.cart, action.payload] };
    case 'REMOVE_FROM_CART':
      return { 
        ...state, 
        cart: state.cart.filter(item => item.id !== action.payload)
      };
    case 'SET_LOADING':
      return { ...state, isLoading: action.payload };
    case 'SET_ERROR':
      return { ...state, error: action.payload };
    default:
      return state;
  }
}

// إنشاء السياق
const AppStateContext = createContext();
const AppDispatchContext = createContext();

// إنشاء المزود
function AppProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, initialState);
  
  return (
    <AppStateContext.Provider value={state}>
      <AppDispatchContext.Provider value={dispatch}>
        {children}
      </AppDispatchContext.Provider>
    </AppStateContext.Provider>
  );
}

// خطافات مخصصة
function useAppState() {
  const context = useContext(AppStateContext);
  if (!context) {
    throw new Error('useAppState يجب استخدامه داخل AppProvider');
  }
  return context;
}

function useAppDispatch() {
  const context = useContext(AppDispatchContext);
  if (!context) {
    throw new Error('useAppDispatch يجب استخدامه داخل AppProvider');
  }
  return context;
}

// مثال للاستخدام
function ProductItem({ product }) {
  const dispatch = useAppDispatch();
  
  const addToCart = () => {
    dispatch({ type: 'ADD_TO_CART', payload: product });
  };
  
  return (
    <div>
      <h3>{product.name}</h3>
      <p>{product.price} ريال</p>
      <button onClick={addToCart}>إضافة للسلة</button>
    </div>
  );
}

function CartSummary() {
  const { cart } = useAppState();
  const dispatch = useAppDispatch();
  
  const removeFromCart = (productId) => {
    dispatch({ type: 'REMOVE_FROM_CART', payload: productId });
  };
  
  return (
    <div>
      <h2>السلة ({cart.length} منتجات)</h2>
      {cart.map(item => (
        <div key={item.id}>
          {item.name} - {item.price} ريال
          <button onClick={() => removeFromCart(item.id)}>إزالة</button>
        </div>
      ))}
    </div>
  );
}

التخزين المستمر (Persistent Storage)

يمكنك تخزين حالة التطبيق محليًا لاستمرارها بين تحديثات الصفحة باستخدام localStorage:

import { useState, useEffect } from 'react';

// خطاف مخصص للتخزين المستمر
function useLocalStorage(key, initialValue) {
  // حالة لتخزين القيمة
  const [storedValue, setStoredValue] = useState(() => {
    try {
      // محاولة استرجاع القيمة من localStorage
      const item = window.localStorage.getItem(key);
      // إذا كانت موجودة، قم بإرجاعها
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  // تعريف وظيفة setValue
  const setValue = (value) => {
    try {
      // تحديث حالة React
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      // تحديث localStorage
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

// مثال للاستخدام
function DarkModeSettings() {
  const [darkMode, setDarkMode] = useLocalStorage('darkMode', false);
  
  useEffect(() => {
    // تطبيق السمة على العنصر root
    document.documentElement.classList.toggle('dark-mode', darkMode);
  }, [darkMode]);
  
  return (
    <div>
      <h2>إعدادات المظهر</h2>
      <label>
        <input
          type="checkbox"
          checked={darkMode}
          onChange={(e) => setDarkMode(e.target.checked)}
        />
        تفعيل الوضع الداكن
      </label>
    </div>
  );
}

التوجيه في React 19

يوفر React 19 دعمًا محسنًا للتوجيه، خاصة مع مكونات الخادم. يمكنك استخدام حلول التوجيه المدمجة مع إطار عمل مثل Next.js، أو مكتبات مستقلة مثل React Router.

React Router v6+

React Router هي أكثر مكتبات التوجيه استخدامًا مع React:

import { BrowserRouter, Routes, Route, Link, useParams, Outlet, useNavigate } from 'react-router-dom';

// مكون الصفحة الرئيسية
function Home() {
  return (
    <div>
      <h1>الصفحة الرئيسية</h1>
      <p>مرحبًا بك في تطبيق React 19</p>
    </div>
  );
}

// مكون صفحة المنتجات
function Products() {
  const products = [
    { id: 1, name: 'منتج 1' },
    { id: 2, name: 'منتج 2' },
    { id: 3, name: 'منتج 3' }
  ];
  
  return (
    <div>
      <h1>المنتجات</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            <Link to={`/products/${product.id}`}>{product.name}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

// مكون صفحة تفاصيل المنتج
function ProductDetail() {
  const { productId } = useParams();
  const navigate = useNavigate();
  
  // في تطبيق حقيقي، ستقوم بجلب بيانات المنتج استنادًا إلى المعرّف
  
  return (
    <div>
      <h1>تفاصيل المنتج {productId}</h1>
      <button onClick={() => navigate(-1)}>العودة</button>
      <button onClick={() => navigate('/products')}>كل المنتجات</button>
    </div>
  );
}

// مكون صفحة الاتصال
function Contact() {
  return (
    <div>
      <h1>اتصل بنا</h1>
      <p>يمكنك التواصل معنا عبر البريد الإلكتروني أو الهاتف.</p>
    </div>
  );
}

// مكون النموذج - Layout
function Layout() {
  return (
    <div>
      <header>
        <nav>
          <ul>
            <li><Link to="/">الرئيسية</Link></li>
            <li><Link to="/products">المنتجات</Link></li>
            <li><Link to="/contact">اتصل بنا</Link></li>
          </ul>
        </nav>
      </header>
      
      <main>
        {/* هنا سيتم عرض محتوى الصفحة الحالية */}
        <Outlet />
      </main>
      
      <footer>
        <p>© 2023 شركتنا. جميع الحقوق محفوظة.</p>
      </footer>
    </div>
  );
}

// إعداد التوجيه في التطبيق
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="products" element={<Products />} />
          <Route path="products/:productId" element={<ProductDetail />} />
          <Route path="contact" element={<Contact />} />
          
          {/* صفحة 404 لعناوين URL غير الموجودة */}
          <Route path="*" element={<h1>404 - الصفحة غير موجودة</h1>} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

التوجيه مع Next.js

يوفر Next.js نظام توجيه مدمج قوي يعمل بشكل جيد مع React 19 ومكونات الخادم:

// app/page.js - الصفحة الرئيسية
export default function HomePage() {
  return (
    <div>
      <h1>الصفحة الرئيسية</h1>
      <p>مرحبًا بك في تطبيق Next.js مع React 19</p>
    </div>
  );
}

// app/products/page.js - صفحة المنتجات
import Link from 'next/link';

export default function ProductsPage() {
  const products = [
    { id: 1, name: 'منتج 1' },
    { id: 2, name: 'منتج 2' },
    { id: 3, name: 'منتج 3' }
  ];
  
  return (
    <div>
      <h1>المنتجات</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            <Link href={`/products/${product.id}`}>{product.name}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

// app/products/[id]/page.js - صفحة تفاصيل المنتج
export default function ProductDetailPage({ params }) {
  const { id } = params;
  
  return (
    <div>
      <h1>تفاصيل المنتج {id}</h1>
      <p>معلومات حول المنتج...</p>
      <Link href="/products">العودة إلى المنتجات</Link>
    </div>
  );
}

// app/layout.js - التخطيط العام للتطبيق
import Link from 'next/link';

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <header>
          <nav>
            <ul>
              <li><Link href="/">الرئيسية</Link></li>
              <li><Link href="/products">المنتجات</Link></li>
              <li><Link href="/contact">اتصل بنا</Link></li>
            </ul>
          </nav>
        </header>
        
        <main>
          {children}
        </main>
        
        <footer>
          <p>© 2023 شركتنا. جميع الحقوق محفوظة.</p>
        </footer>
      </body>
    </html>
  );
}

التنقل البرمجي

يمكنك التنقل برمجيًا باستخدام الأدوات التي توفرها مكتبات التوجيه:

// مع React Router
import { useNavigate } from 'react-router-dom';

function CheckoutButton() {
  const navigate = useNavigate();
  
  const handleCheckout = () => {
    // القيام بعملية ما
    saveOrder().then(() => {
      // التنقل إلى صفحة التأكيد
      navigate('/checkout/confirmation');
    });
  };
  
  return (
    <button onClick={handleCheckout}>
      إتمام عملية الشراء
    </button>
  );
}

// مع Next.js
"use client"
import { useRouter } from 'next/navigation';

function CheckoutButton() {
  const router = useRouter();
  
  const handleCheckout = () => {
    // القيام بعملية ما
    saveOrder().then(() => {
      // التنقل إلى صفحة التأكيد
      router.push('/checkout/confirmation');
    });
  };
  
  return (
    <button onClick={handleCheckout}>
      إتمام عملية الشراء
    </button>
  );
}

حماية المسارات

يمكن تنفيذ حماية المسارات لتقييد الوصول إلى بعض الصفحات:

import { Navigate, useLocation } from 'react-router-dom';

// مكون لحماية المسارات
function ProtectedRoute({ children }) {
  const location = useLocation();
  const isAuthenticated = checkIfUserIsAuthenticated(); // وظيفة تتحقق من حالة المصادقة
  
  if (!isAuthenticated) {
    // إعادة توجيه المستخدم إلى صفحة تسجيل الدخول مع الاحتفاظ بالمسار المطلوب
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  
  return children;
}

// استخدام المكون في التوجيه
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="public" element={<PublicPage />} />
          
          {/* المسارات المحمية */}
          <Route path="dashboard" 
            element={
              <ProtectedRoute>
                <Dashboard />
              </ProtectedRoute>
            } 
          />
          <Route path="profile" 
            element={
              <ProtectedRoute>
                <Profile />
              </ProtectedRoute>
            } 
          />
          
          <Route path="login" element={<Login />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

تحسين الأداء في React 19

يقدم React 19 العديد من التحسينات في الأداء والأدوات التي تساعد المطورين على بناء تطبيقات أسرع وأكثر استجابة.

مكونات الخادم للأداء

إحدى أكبر تحسينات الأداء في React 19 هي مكونات الخادم، التي تساعد في:

  • تقليل حجم الحزمة المرسلة إلى المتصفح
  • تحسين وقت التحميل الأولي للصفحة
  • تقليل الحمل على الأجهزة ذات الموارد المحدودة

التحميل المتوازي باستخدام Suspense

يتيح لك Suspense تعليق عرض مكون حتى يتم استيفاء شرط معين، مثل تحميل البيانات:

import { Suspense } from 'react';
import { fetchProfileData } from './api';

// مكون يحتاج إلى تحميل بيانات
function ProfileDetails({ userId }) {
  // في نموذج البيانات الجديد، هذا سيعلق التنفيذ حتى تكتمل عملية الجلب
  const data = fetchProfileData(userId);
  
  return (
    <div>
      <h2>{data.name}</h2>
      <p>{data.bio}</p>
    </div>
  );
}

// استخدام Suspense في التطبيق
function ProfilePage({ userId }) {
  return (
    <div>
      <h1>الملف الشخصي</h1>
      
      {/* Suspense سيعرض المؤشر أثناء تحميل ProfileDetails */}
      <Suspense fallback={<div>جاري تحميل البيانات...</div>}>
        <ProfileDetails userId={userId} />
      </Suspense>
    </div>
  );
}

تجزئة الكود (Code Splitting)

يمكنك تقسيم الشيفرة إلى حزم أصغر وتحميلها عند الحاجة فقط:

import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// استيراد المكونات بشكل متأخر (lazy loading)
const Home = lazy(() => import('./pages/Home'));
const Products = lazy(() => import('./pages/Products'));
const ProductDetail = lazy(() => import('./pages/ProductDetail'));
const Contact = lazy(() => import('./pages/Contact'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>جاري التحميل...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/products" element={<Products />} />
          <Route path="/products/:id" element={<ProductDetail />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

استخدام useMemo وuseCallback لتحسين الأداء

استخدم هذه الخطافات لتخزين النتائج المحسوبة ووظائف معالجة الأحداث:

import { useState, useMemo, useCallback } from 'react';

function ProductList({ products, onProductSelect }) {
  const [filter, setFilter] = useState('');
  
  // استخدام useMemo لتخزين نتيجة الترشيح
  const filteredProducts = useMemo(() => {
    console.log('حساب المنتجات المفلترة');
    return products.filter(product => 
      product.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [products, filter]); // إعادة الحساب فقط عند تغيير المنتجات أو الفلتر
  
  // استخدام useCallback لتخزين وظائف معالجة الأحداث
  const handleProductClick = useCallback((productId) => {
    console.log('تم النقر على المنتج', productId);
    onProductSelect(productId);
  }, [onProductSelect]); // إعادة الإنشاء فقط عند تغيير onProductSelect
  
  return (
    <div>
      <input
        type="text"
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="ابحث عن منتجات..."
      />
      
      <ul>
        {filteredProducts.map(product => (
          <li 
            key={product.id}
            onClick={() => handleProductClick(product.id)}
          >
            {product.name} - {product.price} ريال
          </li>
        ))}
      </ul>
      
      <p>تم العثور على {filteredProducts.length} منتج</p>
    </div>
  );
}

استخدام ميزة التخزين المؤقت الجديدة

يقدم React 19 نظامًا محسنًا للتخزين المؤقت يسمح بتخزين نتائج العمليات باهظة الثمن:

import { cache } from 'react';
import { db } from './database';

// إنشاء وظيفة محفوظة في الذاكرة المؤقتة
export const getUser = cache(async (userId) => {
  console.log('جلب بيانات المستخدم:', userId);
  const user = await db.users.findUnique({ where: { id: userId } });
  return user;
});

// في مكون الخادم
export async function UserProfile({ userId }) {
  // سيتم استدعاء هذه الوظيفة مرة واحدة فقط لكل userId
  // حتى لو تم استدعاء المكون عدة مرات بنفس المعرّف
  const user = await getUser(userId);
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

تقنيات عامة لتحسين الأداء

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

نصيحة للأداء

استخدم أدوات قياس الأداء مثل React DevTools وLighthouse لتحديد نقاط الضعف في تطبيقك، وركز على تحسين تجربة المستخدم الفعلية بدلاً من التحسين المبكر.

اختبار تطبيقات React 19

الاختبار جزء أساسي من تطوير التطبيقات الموثوقة. يمكن اختبار تطبيقات React 19 على عدة مستويات باستخدام أدوات مختلفة.

أنواع الاختبارات

اختبارات الوحدة

تختبر وحدات منفصلة من الكود (مثل الوظائف والمكونات البسيطة) بمعزل عن بقية التطبيق.

اختبارات التكامل

تختبر كيفية عمل عدة مكونات معًا للتأكد من أنها تتفاعل بشكل صحيح.

اختبارات واجهة المستخدم

تختبر التطبيق من منظور المستخدم، محاكية تفاعلات المستخدم الفعلية.

أدوات الاختبار

  • Jest: إطار عمل الاختبار الأساسي
  • React Testing Library: مكتبة تركز على اختبار المكونات من منظور المستخدم
  • Vitest: بديل سريع لـ Jest متوافق مع Vite
  • Cypress: أداة لاختبارات واجهة المستخدم من البداية إلى النهاية
  • Playwright: حل اختبار متكامل من Microsoft يدعم جميع المتصفحات الرئيسية

اختبار المكونات مع React Testing Library

// Counter.js - المكون المراد اختباره
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <h1>العداد: {count}</h1>
      <button onClick={() => setCount(count + 1)}>زيادة</button>
      <button onClick={() => setCount(count - 1)}>إنقاص</button>
    </div>
  );
}

export default Counter;

// Counter.test.js - ملف الاختبار
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';

describe('مكون العداد', () => {
  test('يعرض العداد بقيمة أولية 0', () => {
    render(<Counter />);
    
    // البحث عن نص "العداد: 0" في المستند
    const counterElement = screen.getByText(/العداد: 0/i);
    expect(counterElement).toBeInTheDocument();
  });
  
  test('يزيد العداد عند النقر على زر الزيادة', () => {
    render(<Counter />);
    
    // البحث عن زر الزيادة والنقر عليه
    const incrementButton = screen.getByText(/زيادة/i);
    fireEvent.click(incrementButton);
    
    // التحقق من تحديث العداد
    const counterElement = screen.getByText(/العداد: 1/i);
    expect(counterElement).toBeInTheDocument();
  });
  
  test('ينقص العداد عند النقر على زر الإنقاص', () => {
    render(<Counter />);
    
    // البحث عن زر الإنقاص والنقر عليه
    const decrementButton = screen.getByText(/إنقاص/i);
    fireEvent.click(decrementButton);
    
    // التحقق من تحديث العداد
    const counterElement = screen.getByText(/العداد: -1/i);
    expect(counterElement).toBeInTheDocument();
  });
});

اختبار الخطافات المخصصة

// useCounter.js - الخطاف المخصص
import { useState } from 'react';

function useCounter(initialValue = 0, step = 1) {
  const [count, setCount] = useState(initialValue);
  
  const increment = () => setCount(prevCount => prevCount + step);
  const decrement = () => setCount(prevCount => prevCount - step);
  const reset = () => setCount(initialValue);
  
  return { count, increment, decrement, reset };
}

export default useCounter;

// useCounter.test.js - ملف الاختبار
import { renderHook, act } from '@testing-library/react';
import useCounter from './useCounter';

describe('خطاف useCounter', () => {
  test('يعود بالقيم والوظائف المتوقعة', () => {
    const { result } = renderHook(() => useCounter());
    
    expect(result.current.count).toBe(0);
    expect(typeof result.current.increment).toBe('function');
    expect(typeof result.current.decrement).toBe('function');
    expect(typeof result.current.reset).toBe('function');
  });
  
  test('يقبل قيمة أولية', () => {
    const { result } = renderHook(() => useCounter(10));
    expect(result.current.count).toBe(10);
  });
  
  test('يزيد العداد بشكل صحيح', () => {
    const { result } = renderHook(() => useCounter(0, 2));
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(2);
  });
  
  test('ينقص العداد بشكل صحيح', () => {
    const { result } = renderHook(() => useCounter(10, 5));
    
    act(() => {
      result.current.decrement();
    });
    
    expect(result.current.count).toBe(5);
  });
  
  test('يعيد تعيين العداد إلى القيمة الأولية', () => {
    const { result } = renderHook(() => useCounter(100));
    
    act(() => {
      result.current.increment();
      result.current.reset();
    });
    
    expect(result.current.count).toBe(100);
  });
});

اختبار مكونات الخادم

لاختبار مكونات الخادم في React 19:

// UserProfile.js - مكون الخادم
import { fetchUserData } from '../lib/api';

export default async function UserProfile({ userId }) {
  const userData = await fetchUserData(userId);
  
  return (
    <div>
      <h1>{userData.name}</h1>
      <p>{userData.email}</p>
    </div>
  );
}

// UserProfile.test.js - ملف الاختبار
import { render } from '@testing-library/react';
import UserProfile from './UserProfile';
import { fetchUserData } from '../lib/api';

// تزييف وحدة الـ API
jest.mock('../lib/api', () => ({
  fetchUserData: jest.fn()
}));

describe('مكون UserProfile', () => {
  test('يعرض بيانات المستخدم بشكل صحيح', async () => {
    // تعيين البيانات المزيفة التي سترجعها وظيفة الـ API المزيفة
    fetchUserData.mockResolvedValue({
      name: 'أحمد محمد',
      email: '[email protected]'
    });
    
    // عرض المكون
    const { findByText } = render(await UserProfile({ userId: '123' }));
    
    // التحقق من أن البيانات تظهر بشكل صحيح
    expect(await findByText('أحمد محمد')).toBeInTheDocument();
    expect(await findByText('[email protected]')).toBeInTheDocument();
    
    // التحقق من استدعاء وظيفة الـ API بالمعلمات الصحيحة
    expect(fetchUserData).toHaveBeenCalledWith('123');
  });
});

اختبارات من البداية إلى النهاية مع Cypress

// cypress/e2e/todo-app.cy.js
describe('تطبيق قائمة المهام', () => {
  beforeEach(() => {
    // زيارة التطبيق قبل كل اختبار
    cy.visit('http://localhost:3000');
  });
  
  it('يعرض العنوان الصحيح', () => {
    cy.get('h1').should('contain', 'قائمة المهام');
  });
  
  it('يسمح بإضافة مهمة جديدة', () => {
    // كتابة نص المهمة
    cy.get('input[placeholder="أضف مهمة جديدة..."]')
      .type('مهمة اختبار جديدة');
    
    // النقر على زر الإضافة
    cy.get('button').contains('إضافة').click();
    
    // التحقق من إضافة المهمة إلى القائمة
    cy.get('li').should('contain', 'مهمة اختبار جديدة');
  });
  
  it('يمكن وضع علامة على المهمة كمكتملة', () => {
    // إضافة مهمة جديدة
    cy.get('input[placeholder="أضف مهمة جديدة..."]')
      .type('مهمة للإكمال');
    cy.get('button').contains('إضافة').click();
    
    // النقر على المهمة لوضع علامة عليها كمكتملة
    cy.get('li').contains('مهمة للإكمال').click();
    
    // التحقق من أن المهمة تحمل فئة "completed"
    cy.get('li').contains('مهمة للإكمال')
      .should('have.class', 'completed');
  });
  
  it('يمكن حذف المهمة', () => {
    // إضافة مهمة جديدة
    cy.get('input[placeholder="أضف مهمة جديدة..."]')
      .type('مهمة للحذف');
    cy.get('button').contains('إضافة').click();
    
    // التحقق من وجود المهمة
    cy.get('li').should('contain', 'مهمة للحذف');
    
    // النقر على زر الحذف
    cy.get('li').contains('مهمة للحذف')
      .find('button').contains('حذف').click();
    
    // التحقق من أن المهمة لم تعد موجودة
    cy.get('li').should('not.contain', 'مهمة للحذف');
  });
});

أفضل الممارسات في React 19

اتباع أفضل الممارسات يساعدك على بناء تطبيقات React أكثر قابلية للصيانة وأداءً وسهولة في الفهم.

تنظيم الكود والهيكلة

هيكل المشروع المقترح

src/
├── components/          # المكونات المشتركة
│   ├── Button/
│   │   ├── Button.jsx   # كود المكون
│   │   ├── Button.test.jsx # اختبارات المكون
│   │   └── index.js     # ملف التصدير
│   └── ...
├── hooks/               # الخطافات المخصصة
├── layouts/             # تخطيطات الصفحات
├── pages/               # صفحات التطبيق (أو الطرق)
├── features/            # المكونات المرتبطة بميزات محددة
├── utils/               # وظائف مساعدة
├── services/            # خدمات API والاتصال بالخادم
├── contexts/            # سياقات React
├── types/               # تعريفات TypeScript
└── assets/              # الموارد الثابتة (الصور، الخطوط، إلخ)

أفضل الممارسات الهيكلية

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

أفضل ممارسات الأداء

  • استخدام مكونات الخادم للمحتوى الثابت أو البيانات المعتمدة على الخادم
  • تجزئة الكود (Code Splitting) لتقليل حجم الحزمة الأولية
  • التخزين المؤقت (Memoization) للعمليات الحسابية المكلفة باستخدام useMemo وuseCallback
  • تجنب إعادة العرض غير الضرورية باستخدام React.memo للمكونات التي لا تتغير بشكل متكرر
  • الصور والموارد المحسنة لتقليل وقت التحميل
  • التحميل المتوازي باستخدام Suspense للحصول على واجهة مستخدم أكثر استجابة

أفضل ممارسات إدارة الحالة

  • الحفاظ على الحالة في أدنى مستوى ممكن لتقليل إعادة العرض غير الضرورية
  • استخدام useReducer للحالات المعقدة بدلاً من عدة متغيرات useState
  • تجزئة سياقات التطبيق إلى أجزاء أصغر بدلاً من سياق واحد كبير
  • الانتقال إلى مكتبات إدارة الحالة فقط عندما تصبح الحلول المدمجة غير كافية
  • استخدام التحديثات المتفائلة (Optimistic Updates) لتحسين تجربة المستخدم

مثال على التنظيم الجيد للمكون

// src/features/products/ProductCard.jsx
import { useState, useCallback, memo } from 'react';
import PropTypes from 'prop-types';
import { useCart } from '../../hooks/useCart';
import { Button } from '../../components/Button';
import { formatCurrency } from '../../utils/formatters';

// مكون ProductCard - مسؤول عن عرض بطاقة منتج واحدة
function ProductCard({ product }) {
  const { addToCart } = useCart();
  const [isHovered, setIsHovered] = useState(false);
  
  // استخدام useCallback لمعالجات الأحداث
  const handleMouseEnter = useCallback(() => {
    setIsHovered(true);
  }, []);
  
  const handleMouseLeave = useCallback(() => {
    setIsHovered(false);
  }, []);
  
  const handleAddToCart = useCallback(() => {
    addToCart(product.id);
  }, [addToCart, product.id]);
  
  // فصل المنطق الشرطي للتصميم في متغيرات أو وظائف منفصلة
  const cardClasses = `product-card ${isHovered ? 'product-card--hovered' : ''}`;
  
  return (
    <div 
      className={cardClasses}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      <img 
        src={product.imageUrl} 
        alt={product.name} 
        className="product-card__image"
      />
      
      <div className="product-card__content">
        <h3 className="product-card__title">{product.name}</h3>
        <p className="product-card__description">{product.description}</p>
        <p className="product-card__price">
          {formatCurrency(product.price)}
        </p>
        
        <Button onClick={handleAddToCart} variant="primary">
          إضافة إلى السلة
        </Button>
      </div>
    </div>
  );
}

// تعريف PropTypes للتحقق من النوع
ProductCard.propTypes = {
  product: PropTypes.shape({
    id: PropTypes.string.isRequired,
    name: PropTypes.string.isRequired,
    description: PropTypes.string.isRequired,
    price: PropTypes.number.isRequired,
    imageUrl: PropTypes.string.isRequired
  }).isRequired
};

// تصدير المكون مع memo لتجنب إعادة العرض غير الضرورية
export default memo(ProductCard);
    

قائمة مرجعية لمراجعة الكود

قبل الانتهاء من العمل، تحقق من:

  • هل المكونات تتبع مبدأ المسؤولية الواحدة (Single Responsibility)؟
  • هل يتم استخراج المنطق المشترك في خطافات أو وظائف مساعدة؟
  • هل يتم التعامل مع جميع حالات الخطأ والتحميل بشكل صحيح؟
  • هل يتم التحقق من أنواع Props باستخدام PropTypes أو TypeScript؟
  • هل التسمية واضحة ومتسقة؟
  • هل يتم استخدام تجزئة الكود (Code Splitting) للمكونات الكبيرة؟
  • هل تم كتابة اختبارات كافية؟
  • هل الكود متوافق مع متطلبات إمكانية الوصول (Accessibility)?

أفضل ممارسات التصميم

  • استخدام مكتبات مكونات مثل MUI، Chakra UI، أو Tailwind CSS لتسريع التطوير
  • اعتماد نظام تصميم متسق للألوان والخطوط والمسافات
  • تطبيق تصميم متجاوب (Responsive) لجميع أحجام الشاشات
  • الاهتمام بقابلية الوصول (Accessibility) لضمان أن الجميع يمكنهم استخدام تطبيقك
  • اعتماد الوضع الداكن كخيار لتحسين تجربة المستخدم

أفضل ممارسات الأمان

  • تجنب استخدام dangerouslySetInnerHTML إلا عند الضرورة القصوى مع تنظيف المدخلات
  • التحقق من المدخلات وترميزها لمنع هجمات XSS
  • استخدام وحدات CSRF للطلبات لمنع هجمات التزوير
  • استخدام HTTPS لحماية البيانات أثناء النقل
  • تجنب تخزين معلومات حساسة في localStorage أو sessionStorage

التعاون وأفضل ممارسات العمل الجماعي

  • توثيق القرارات المهمة في ملفات README أو Wiki
  • استخدام أدوات تنسيق الكود مثل Prettier لضمان التناسق
  • اعتماد ESLint مع قواعد متفق عليها لتحسين جودة الكود
  • كتابة مراجعات الكود البناءة مع التركيز على المشكلات وليس الأشخاص
  • استخدام Git Flow أو استراتيجية فروع متفق عليها
  • تشغيل الاختبارات الآلية قبل الدمج في الفروع الرئيسية

ملاحظة ختامية

تذكر أن أفضل الممارسات تتطور مع تطور التكنولوجيا. استمر في التعلم والتكيف مع أحدث الأساليب والتقنيات لبناء تطبيقات React أفضل.