دليل مفصل وشامل لجميع ميزات ومفاهيم React 19 مع شرح وأمثلة عملية.
React 19 هو أحدث إصدار من مكتبة React لبناء واجهات المستخدم. يجلب هذا الإصدار تحسينات كبيرة في الأداء وتجربة المطور، مع التركيز على تعزيز قدرات مكونات الخادم والعميل.
يقدم 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 بيئة تطوير أسرع ويمكن استخدامه مع 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
JSX هو امتداد بناء الجملة لـ JavaScript الذي يسمح بكتابة شيفرة تشبه HTML ضمن ملفات JavaScript. في React 19، تم تحسين دعم JSX مع تبسيط القواعد والتعامل مع الحالات الخاصة.
// مثال بسيط لـ JSX
function Greeting() {
const name = 'أحمد';
return (
<div className="greeting">
<h1>مرحباً، {name}!</h1>
<p>يسعدنا وجودك معنا.</p>
</div>
);
}
في المثال أعلاه، يمكنك ملاحظة:
{}
لتضمين تعبيرات JavaScriptclassName
بدلاً من class
(لأن class هي كلمة محجوزة في JavaScript)React.createElement()
function ConditionalGreeting({ isLoggedIn, username }) {
return (
<div>
{isLoggedIn ? (
<h1>مرحباً، {username}!</h1>
) : (
<h1>مرحباً، زائرنا العزيز!</h1>
)}
</div>
);
}
function ItemList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
ملاحظة مهمة: دائمًا استخدم خاصية key
فريدة عند عرض قوائم في React لمساعدة خوارزمية التوفيق.
function ClickButton() {
const handleClick = () => {
alert('تم النقر على الزر!');
};
return (
<button onClick={handleClick}>
انقر هنا
</button>
);
}
المكونات هي اللبنات الأساسية لأي تطبيق 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 هي البيانات التي تمرر من مكون أب إلى مكون ابن، وهي للقراءة فقط (غير قابلة للتعديل):
// تعريف المكون
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
function UserProfile({ name, age, email }) {
return (
<div>
<h2>{name}</h2>
<p>العمر: {age}</p>
<p>البريد الإلكتروني: {email}</p>
</div>
);
}
// تعيين قيم افتراضية للـ 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: 'غير متوفر'
};
يمكن تمرير المحتوى بين علامات فتح وإغلاق المكون ويمكن الوصول إليه من خلال 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>
);
}
الخطافات في React تسمح لك باستخدام حالة ومميزات React الأخرى بدون كتابة مكونات فئة. في React 19، تمت إضافة خطافات جديدة وتحسين الموجودة.
يسمح لك بإضافة حالة محلية إلى المكونات الوظيفية:
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>
);
}
يسمح لك بتنفيذ "التأثيرات الجانبية" في المكونات الوظيفية (مثل جلب البيانات أو الاشتراك في الأحداث):
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>
);
}
يسمح لك بالوصول إلى قيم السياق (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>
);
}
بديل لـ 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 عدة خطافات جديدة لتحسين تجربة المطور:
تعد مكونات الخادم (Server Components) من أهم ميزات React 19، حيث تمكّن المطورين من إنشاء تطبيقات أسرع وأكثر كفاءة من خلال تقسيم العمل بين الخادم والعميل.
مكونات الخادم هي مكونات React يتم تشغيلها وعرضها على الخادم فقط. يتم إرسال نتائج العرض إلى المتصفح بدلاً من الكود نفسه، مما يؤدي إلى:
لتمييز مكون العميل في 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 العديد من الخيارات لإدارة حالة التطبيق، بدءًا من الحلول البسيطة مثل useState وحتى حلول إدارة الحالة الشاملة للتطبيقات الكبيرة.
حالة داخل مكون واحد، مناسبة للبيانات التي تنتمي إلى مكون واحد فقط.
مثال: useState, useReducer
حالة مشتركة بين عدة مكونات، غالبًا ما تكون متداخلة.
مثال: رفع الحالة، Context API
حالة على مستوى التطبيق بأكمله، مستخدمة عبر مكونات غير مرتبطة.
مثال: Context API + useReducer، مكتبات خارجية
توفر 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>
);
}
يمكنك تخزين حالة التطبيق محليًا لاستمرارها بين تحديثات الصفحة باستخدام 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 دعمًا محسنًا للتوجيه، خاصة مع مكونات الخادم. يمكنك استخدام حلول التوجيه المدمجة مع إطار عمل مثل Next.js، أو مكتبات مستقلة مثل React Router.
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 نظام توجيه مدمج قوي يعمل بشكل جيد مع 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 هي مكونات الخادم، التي تساعد في:
يتيح لك 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>
);
}
يمكنك تقسيم الشيفرة إلى حزم أصغر وتحميلها عند الحاجة فقط:
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>
);
}
استخدم هذه الخطافات لتخزين النتائج المحسوبة ووظائف معالجة الأحداث:
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>
);
}
استخدم أدوات قياس الأداء مثل React DevTools وLighthouse لتحديد نقاط الضعف في تطبيقك، وركز على تحسين تجربة المستخدم الفعلية بدلاً من التحسين المبكر.
الاختبار جزء أساسي من تطوير التطبيقات الموثوقة. يمكن اختبار تطبيقات React 19 على عدة مستويات باستخدام أدوات مختلفة.
تختبر وحدات منفصلة من الكود (مثل الوظائف والمكونات البسيطة) بمعزل عن بقية التطبيق.
تختبر كيفية عمل عدة مكونات معًا للتأكد من أنها تتفاعل بشكل صحيح.
تختبر التطبيق من منظور المستخدم، محاكية تفاعلات المستخدم الفعلية.
// 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/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 أكثر قابلية للصيانة وأداءً وسهولة في الفهم.
src/
├── components/ # المكونات المشتركة
│ ├── Button/
│ │ ├── Button.jsx # كود المكون
│ │ ├── Button.test.jsx # اختبارات المكون
│ │ └── index.js # ملف التصدير
│ └── ...
├── hooks/ # الخطافات المخصصة
├── layouts/ # تخطيطات الصفحات
├── pages/ # صفحات التطبيق (أو الطرق)
├── features/ # المكونات المرتبطة بميزات محددة
├── utils/ # وظائف مساعدة
├── services/ # خدمات API والاتصال بالخادم
├── contexts/ # سياقات React
├── types/ # تعريفات TypeScript
└── assets/ # الموارد الثابتة (الصور، الخطوط، إلخ)
// 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);
تذكر أن أفضل الممارسات تتطور مع تطور التكنولوجيا. استمر في التعلم والتكيف مع أحدث الأساليب والتقنيات لبناء تطبيقات React أفضل.