🎯 ما ستتعلمه في هذا الدرس
- فهم React Router ولماذا نحتاجه في تطبيقات React
- إعداد التوجيه الأساسي مع BrowserRouter و Routes
- المسارات الديناميكية (Dynamic Routes) مع useParams
- التنقل البرمجي باستخدام useNavigate
- حماية المسارات (Protected Routes) للصفحات الخاصة
- التحميل الكسول (Lazy Loading) لتحسين الأداء
- تنظيم هيكل التطبيق في مشاريع حقيقية
📖 ما هو React Router ولماذا هو ضروري؟
React Router هي مكتبة قوية جداً للتنقل والملاحة في تطبيقات React. في تطبيقات الويب التقليدية (مثل PHP أو WordPress)، كل صفحة جديدة تتطلب تحميل كامل من السيرفر — HTML جديد، CSS جديد، JavaScript جديد. هذا يستغرق 1-3 ثوانٍ ويُرهق السيرفر.
React Router تسمح ببناء Single Page Applications (SPA) — تطبيقات صفحة واحدة تتغير محتوياتها ديناميكياً بدون إعادة تحميل الصفحة بالكامل. عندما تنتقل من "الرئيسية" إلى "من نحن"، React Router يبدل المكون المعروض فقط — بدون طلب جديد من السيرفر. هذا يجعل التطبيق أسرع بـ 10 أضعاف وأكثر سلاسة.
في مشروع لوحة تحكم (Admin Dashboard) عملته لشركة صغيرة، كان هناك 15 صفحة مختلفة. بدون React Router، كل انتقال كان يستغرق 2 ثانية. مع React Router، الانتقال أصبح فورياً — والعميل فرح جداً بالسرعة. هذا الفرق يُحدث!
لماذا لا نستخدم روابط HTML العادية (<a href>)؟
الروابط العادية (<a href="/about">) تُسبب إعادة تحميل الصفحة كاملة — تفقد State، تُعيد تشغيل JavaScript، وتُرهق السيرفر. <Link> من React Router يغير URL في المتصفح بدون إعادة التحميل، ويُبدل المكون المعروض فقط.
⚡ التثبيت والإعداد الكامل
npm install react-router-dom
import {
BrowserRouter,
Routes,
Route,
Link,
NavLink,
useParams,
useNavigate,
useLocation,
Outlet,
Navigate
} from 'react-router-dom';
💡 نصيحة: من الإصدار 6 (2021)، React Router تغيرت بشكل كبير. Switch أصبح Routes، وcomponent أصبح element. تأكد من استخدام الإصدار 6+ — الإصدارات القديمة مختلفة جداً!
🎯 الأساسيات — إعداد التوجيه في تطبيق React
React Router تعتمد على مفهوم بسيط: كل URL path يرتبط بمكون (Component) معين. عندما يزور المستخدم /about، React Router يعرض مكون About. عندما يزور /users، يعرض مكون Users.
هيكل التطبيق الأساسي
import { BrowserRouter, Routes, Route, Link, NavLink } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Users from './pages/Users';
import UserDetail from './pages/UserDetail';
import Dashboard from './pages/Dashboard';
import Login from './pages/Login';
import NotFound from './pages/NotFound';
function App() {
return (
<BrowserRouter>
<nav className="navbar">
<div className="nav-brand">🚀 تطبيقي</div>
<div className="nav-links">
<NavLink
to="/"
className={({ isActive }) =>
isActive ? 'nav-link active' : 'nav-link'
}
end
>
🏠 الرئيسية
</NavLink>
<NavLink
to="/about"
className={({ isActive }) =>
isActive ? 'nav-link active' : 'nav-link'
}
>
ℹ️ من نحن
</NavLink>
<NavLink
to="/users"
className={({ isActive }) =>
isActive ? 'nav-link active' : 'nav-link'
}
>
👥 المستخدمين
</NavLink>
<NavLink
to="/dashboard"
className={({ isActive }) =>
isActive ? 'nav-link active' : 'nav-link'
}
>
📊 لوحة التحكم
</NavLink>
</div>
</nav>
<main className="main-content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<Users />} />
<Route path="/users/:id" element={<UserDetail />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="*" element={<NotFound />} />
</Routes>
</main>
</BrowserRouter>
);
}
export default App;
⚠️ خطأ شائع 80% من المبتدئين: يستخدمون <a href="/about"> بدلاً من <Link to="/about">. <a> يُسبب إعادة تحميل الصفحة كاملة — تفقد State ويُعيد تشغيل JavaScript. استخدم Link أو NavLink دائماً!
🔗 Dynamic Route Parameters — المسارات الديناميكية
أحياناً تريد مساراً يتقبل معاملات متغيرة من URL. مثلاً، لعرض تفاصيل مستخدم معين: /users/123 أو /users/456. React Router يتيح هذا باستخدام :parameter في المسار.
في مشروع متجر إلكتروني عملته، كان هناك 500 منتج. بدلاً من إنشاء 500 مسار منفصل، استخدمت مساراً ديناميكياً واحداً: /products/:productId. المكون ProductDetail يستقبل productId ويجلب بيانات المنتج من API.
استخدام useParams
import { useParams, useNavigate } from 'react-router-dom';
import { useState, useEffect } from 'react';
function UserDetail() {
const { id } = useParams();
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const navigate = useNavigate();
useEffect(() => {
const fetchUser = async () => {
try {
const res = await fetch(`https://api.example.com/users/${id}`);
if (!res.ok) throw new Error('المستخدم غير موجود');
const data = await res.json();
setUser(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchUser();
}, [id]);
if (loading) return <div className="spinner">جاري التحميل...</div>;
if (error) return (
<div className="error-container">
<h2>❌ {error}</h2>
<button onClick={() => navigate('/users')}>
← العودة للقائمة
</button>
</div>
);
return (
<div className="user-detail">
<button onClick={() => navigate('/users')} className="back-btn">
← العودة
</button>
<img src={user.avatar} alt={user.name} className="user-avatar" />
<h1>{user.name}</h1>
<p>📧 {user.email}</p>
<p>📱 {user.phone}</p>
<p>🏢 {user.department}</p>
<div className="user-stats">
<div className="stat">
<span className="stat-value">{user.projects}</span>
<span className="stat-label">مشاريع</span>
</div>
<div className="stat">
<span className="stat-value">{user.tasks}</span>
<span className="stat-label">مهام</span>
</div>
</div>
</div>
);
}
<Route path="/users/:id" element={<UserDetail />} />
💡 نصيحة احترافية: دائماً استخدم useEffect مع [id] كـ dependency عند جلب بيانات ديناميكية. إذا نسيّت، البيانات لن تتحدث عند تغيير المستخدم — وهذا خطأ شائع جداً!
🔄 التنقل البرمجي — useNavigate
أحياناً تريد التنقل إلى صفحة أخرى برمجياً بعد حدث معين — بعد تسجيل الدخول الناجح، بعد حفظ البيانات، أو بعد حذف عنصر. useNavigate يسمح بهذا.
مثال: تسجيل دخول مع إعادة توجيه
import { useNavigate, useLocation } from 'react-router-dom';
import { useState } from 'react';
function LoginPage() {
const navigate = useNavigate();
const location = useLocation();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const from = location.state?.from?.pathname || '/dashboard';
const handleLogin = async (e) => {
e.preventDefault();
setError('');
setIsLoading(true);
try {
const response = await fetch('https://api.example.com/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'بيانات الدخول غير صحيحة');
}
localStorage.setItem('token', data.token);
localStorage.setItem('user', JSON.stringify(data.user));
navigate(from, { replace: true });
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
return (
<div className="login-page">
<h1>🔐 تسجيل الدخول</h1>
{error && <div className="error">{error}</div>}
<form onSubmit={handleLogin}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="البريد الإلكتروني"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="كلمة المرور"
required
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'جاري الدخول...' : 'دخول'}
</button>
</form>
<p>
ليس لديك حساب؟{' '}
<Link to="/register">سجل الآن</Link>
</p>
</div>
);
}
🔒 Protected Routes — حماية المسارات الخاصة
في التطبيقات الحقيقية، بعض الصفحات تحتاج تسجيل دخول — مثل لوحة التحكم، الإعدادات، أو صفحة الملف الشخصي. Protected Routes تتحقق من صلاحية المستخدم قبل عرض الصفحة.
في مشروع لوحة تحكم عملته، كان هناك 8 صفحات محمية. بدلاً من تكرار كود التحقق في كل صفحة، أنشأت مكون ProtectedRoute واحداً يُستخدم في كل المسارات المحمية.
import { Navigate, useLocation } from 'react-router-dom';
function ProtectedRoute({ children }) {
const location = useLocation();
const token = localStorage.getItem('token');
const user = localStorage.getItem('user');
if (!token || !user) {
return <Navigate
to="/login"
state={{ from: location }}
replace
/>;
}
return children;
}
function App() {
return (
<BrowserRouter>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
}
/>
<Route
path="/settings"
element={
<ProtectedRoute>
<Settings />
</ProtectedRoute>
}
/>
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
🎯 نصيحة احترافية: في التطبيقات الحقيقية، لا تستخدم localStorage وحده للتحقق. تحقق من صلاحية التوكن مع السيرفر عند تحميل الصفحة. استخدم Context API أو Redux لتخزين حالة المستخدم بشكل مركزي.
🌳 Nested Routes — المسارات المتداخلة
أحياناً تريد صفحة رئيسية تحتوي على عدة أقسام فرعية. مثلاً، لوحة التحكم تحتوي على "الإحصائيات"، "المنتجات"، "الطلبات" — كلها داخل نفس التصميم (Sidebar + Header). Nested Routes تتيح هذا.
import { Outlet, NavLink } from 'react-router-dom';
function Dashboard() {
return (
<div className="dashboard-layout">
<aside className="dashboard-sidebar">
<h3>لوحة التحكم</h3>
<nav>
<NavLink to="/dashboard" end>📊 الإحصائيات</NavLink>
<NavLink to="/dashboard/products">📦 المنتجات</NavLink>
<NavLink to="/dashboard/orders">🛒 الطلبات</NavLink>
<NavLink to="/dashboard/users">👥 المستخدمين</NavLink>
</nav>
</aside>
<main className="dashboard-content">
<Outlet />
</main>
</div>
);
}
<Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>}>
<Route index element={<DashboardStats />} />
<Route path="products" element={<ProductList />} />
<Route path="orders" element={<OrderList />} />
<Route path="users" element={<UserList />} />
<Route path="products/:id" element={<ProductEdit />} />
</Route>
⚡ Lazy Loading — التحميل الكسول لتحسين الأداء
في التطبيقات الكبيرة (20+ صفحة)، تحميل كل الصفحات دفعة واحدة يُبطئ التطبيق. Lazy Loading يحمّل الصفحة فقط عندما يحتاجها المستخدم — يقلل حجم التحميل الأولي بنسبة 70%!
import { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Users = lazy(() => import('./pages/Users'));
const UserDetail = lazy(() => import('./pages/UserDetail'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Login = lazy(() => import('./pages/Login'));
function LoadingSpinner() {
return (
<div className="loading-container">
<div className="spinner"></div>
<p>جاري تحميل الصفحة...</p>
</div>
);
}
function App() {
return (
<BrowserRouter>
<Navbar />
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<Users />} />
<Route path="/users/:id" element={<UserDetail />} />
<Route path="/login" element={<Login />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
🎯 متى تستخدم Lazy Loading؟ في التطبيقات التي تحتوي على 10+ صفحات. لا تستخدمه في صفحات صغيرة (أقل من 5 صفحات) — التعقيد لا يستحق الفائدة. في مشروعي (15 صفحة)، Lazy Loading قلل حجم التحميل الأولي من 800KB إلى 250KB!
🏠 تنظيم هيكل التطبيق في مشاريع حقيقية
النمط الأفضل للتطبيقات الكبيرة هو تنظيم المسارات في ملف منفصل وحفظ الصفحات في مجلد pages (أو views).
src/
components/ ← مكونات صغيرة قابلة لإعادة الاستخدام
Navbar/
Navbar.js
Navbar.css
ProtectedRoute/
ProtectedRoute.js
LoadingSpinner/
LoadingSpinner.js
pages/ ← صفحات كاملة (مكونات كبيرة)
Home/
Home.js
Home.css
About/
About.js
Users/
Users.js
UserDetail.js
Dashboard/
Dashboard.js
DashboardStats.js
ProductList.js
OrderList.js
Login/
Login.js
NotFound/
NotFound.js
App.js ← المسارات الرئيسية
index.js
📊 مقارنة سريعة: Link vs useNavigate vs Navigate
| الأداة |
الاستخدام |
مثال |
Link |
روابط نصية/أزرار في JSX |
القائمة، روابط التنقل |
NavLink |
روابط مع تنسيق نشط تلقائياً |
Navbar مع underline للصفحة الحالية |
useNavigate |
التنقل برمجياً بعد حدث |
بعد تسجيل دخول، حذف عنصر |
Navigate |
إعادة توجيه مباشرة في JSX |
ProtectedRoute، redirect |
🚀 تمرين عملي: بناء تطبيق مدونة متكامل
قبل الانتقال للدرس التالي، جرب بناء تطبيق مدونة بسيط يستخدم كل ما تعلمته:
📝 تلميح: ابدأ بـ 3 صفحات فقط (Home, About, PostDetail). ثم أضف Login وProtectedRoute. ثم Nested Routes. لا تحاول كل شيء دفعة واحدة — اختبر كل خطوة!
📚 الخلاصة والخطوات التالية
في هذا الدرس تعلمت:
- ✅ React Router — التنقل بدون إعادة تحميل الصفحة في SPA
- ✅ Link و NavLink — روابط بدون إعادة تحميل مع تنسيق نشط
- ✅ Dynamic Routes — مسارات ديناميكية مع useParams (مثل /users/:id)
- ✅ useNavigate — التنقل برمجياً بعد أحداث (تسجيل دخول، حفظ)
- ✅ ProtectedRoute — حماية الصفحات الخاصة بتسجيل الدخول
- ✅ Nested Routes — مسارات متداخلة مع Outlet
- ✅ Lazy Loading — تحميل الصفحات عند الحاجة لتحسين الأداء
الدرس القادم: سنتعلم إدارة الحالة العالمية (State Management) باستخدام Context API و Redux Toolkit — كيف نُبقي بيانات المستخدم متاحة في كل المكونات بدون Props Drilling!
🎯 مهمة قبل الدرس القادم: أكمل تمرين المدونة. جرب إضافة Nested Routes في Dashboard (Posts, Comments). شارك الكود على واتساب 01229068356 وسنراجعه معك!