React Router: التوجيه من جانب العميل
مقدمة إلى React Router
React Router هي مكتبة التوجيه القياسية لتطبيقات React. تمكن من التنقل بين طرق العرض المختلفة لتطبيقك، وتدير سجل المتصفح، وتحافظ على تزامن واجهة المستخدم مع عنوان URL—كل ذلك بدون إعادة تحميل الصفحة.
ملاحظة: يغطي هذا الدرس React Router الإصدار 6، والذي قدم تغييرات كبيرة عن الإصدار 5. تأكد من تثبيت الإصدار الصحيح: npm install react-router-dom@6
إعداد React Router
أولاً، قم بتثبيت React Router في مشروعك:
# تثبيت React Router
npm install react-router-dom
# أو باستخدام Yarn
yarn add react-router-dom
يتضمن الإعداد الأساسي تغليف تطبيقك بـ BrowserRouter وتعريف المسارات:
// src/main.jsx أو src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
تعريف المسارات باستخدام Routes و Route
في React Router الإصدار 6، استخدم مكون Routes لتغليف جميع مكونات Route:
// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';
function App() {
return (
<div className="app">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
);
}
export default App;
نصيحة: المسار path="*" يعمل كمسار شامل لعناوين URL غير المطابقة، مثالي لصفحات 404. يجب أن يكون دائمًا المسار الأخير.
التنقل باستخدام Link و NavLink
يوفر React Router مكونات Link و NavLink للتنقل بدون إعادة تحميل الصفحة بالكامل:
// src/components/Navigation.jsx
import { Link, NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
{/* Link الأساسي - بدون تنسيق بناءً على الحالة النشطة */}
<Link to="/">الرئيسية</Link>
<Link to="/about">عن</Link>
{/* NavLink - يتلقى فئة "active" عندما يتطابق المسار */}
<NavLink
to="/"
className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
>
الرئيسية
</NavLink>
<NavLink
to="/about"
style={({ isActive }) => ({
color: isActive ? '#ff6b6b' : '#333',
fontWeight: isActive ? 'bold' : 'normal'
})}
>
عن
</NavLink>
</nav>
);
}
export default Navigation;
الفرق الرئيسي: Link للتنقل الأساسي، بينما NavLink يوفر تنسيق الحالة النشطة، مما يجعله مثاليًا لقوائم التنقل.
المسارات المتداخلة
React Router الإصدار 6 يجعل التوجيه المتداخل أكثر بديهية مع مكون Outlet:
// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './pages/Home';
import Dashboard from './pages/Dashboard';
import Profile from './pages/dashboard/Profile';
import Settings from './pages/dashboard/Settings';
import Analytics from './pages/dashboard/Analytics';
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
{/* المسارات المتداخلة تحت /dashboard */}
<Route path="dashboard" element={<Dashboard />}>
<Route index element={<Profile />} />
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
<Route path="analytics" element={<Analytics />} />
</Route>
</Route>
</Routes>
);
}
// src/components/Layout.jsx
import { Outlet } from 'react-router-dom';
import Navigation from './Navigation';
import Footer from './Footer';
function Layout() {
return (
<div>
<Navigation />
<main>
<Outlet /> {/* تُعرض المسارات الفرعية هنا */}
</main>
<Footer />
</div>
);
}
// src/pages/Dashboard.jsx
import { Outlet, Link } from 'react-router-dom';
function Dashboard() {
return (
<div className="dashboard">
<aside>
<Link to="/dashboard/profile">الملف الشخصي</Link>
<Link to="/dashboard/settings">الإعدادات</Link>
<Link to="/dashboard/analytics">التحليلات</Link>
</aside>
<section>
<Outlet /> {/* تُعرض مسارات لوحة التحكم المتداخلة هنا */}
</section>
</div>
);
}
معاملات URL
المسارات الديناميكية تستخدم معاملات URL لتمرير البيانات من خلال عنوان URL:
// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import UserProfile from './pages/UserProfile';
import BlogPost from './pages/BlogPost';
function App() {
return (
<Routes>
{/* معامل واحد */}
<Route path="/users/:userId" element={<UserProfile />} />
{/* معاملات متعددة */}
<Route path="/blog/:category/:postId" element={<BlogPost />} />
{/* معاملات اختيارية مع ? */}
<Route path="/products/:productId/:variant?" element={<Product />} />
</Routes>
);
}
// src/pages/UserProfile.jsx
import { useParams } from 'react-router-dom';
import { useEffect, useState } from 'react';
function UserProfile() {
const { userId } = useParams();
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// جلب بيانات المستخدم بناءً على userId
fetch(`https://api.example.com/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, [userId]);
if (loading) return <div>جاري تحميل المستخدم...</div>
if (!user) return <div>المستخدم غير موجود</div>
return (
<div>
<h1>ملف {user.name} الشخصي</h1>
<p>معرف المستخدم: {userId}</p>
<p>البريد الإلكتروني: {user.email}</p>
</div>
);
}
// src/pages/BlogPost.jsx
import { useParams } from 'react-router-dom';
function BlogPost() {
const { category, postId } = useParams();
return (
<div>
<h1>مقال المدونة</h1>
<p>الفئة: {category}</p>
<p>معرف المقال: {postId}</p>
</div>
);
}
تحذير: معاملات URL دائمًا نصوص. إذا كنت بحاجة إلى أرقام، قم بتحويلها: const id = parseInt(params.userId, 10);
مسارات الفهرس
مسارات الفهرس تُعرض عندما يتطابق المسار الأصل تمامًا:
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
{/* يُعرض هذا عند "/" تمامًا */}
<Route index element={<Home />} />
{/* يُعرض هذا عند "/about" */}
<Route path="about" element={<About />} />
<Route path="products" element={<ProductsLayout />}>
{/* يُعرض هذا عند "/products" تمامًا */}
<Route index element={<ProductsList />} />
{/* يُعرض هذا عند "/products/:id" */}
<Route path=":id" element={<ProductDetail />} />
</Route>
</Route>
</Routes>
);
}
تمرين 1: إعداد التوجيه الأساسي
المهمة: أنشئ تطبيقًا متعدد الصفحات بالمسارات التالية:
- الصفحة الرئيسية عند
/ - صفحة عن عند
/about - صفحة الخدمات عند
/services - صفحة الاتصال عند
/contact - صفحة 404 للمسارات غير المطابقة
أضف شريط تنقل بمكونات NavLink التي تبرز الصفحة النشطة.
تمرين 2: المسارات المتداخلة مع المنتجات
المهمة: أنشئ قسم منتجات بمسارات متداخلة:
/products- يعرض قائمة فئات المنتجات/products/:categoryId- يعرض المنتجات في تلك الفئة/products/:categoryId/:productId- يعرض تفاصيل المنتج
استخدم بيانات عينة وخطاف useParams لعرض المعلومات الصحيحة.
تمرين 3: لوحة تحكم المستخدم
المهمة: بناء لوحة تحكم مستخدم بمسارات متداخلة:
/dashboard- تخطيط لوحة التحكم مع الشريط الجانبي/dashboard/overview- صفحة النظرة العامة (مسار الفهرس)/dashboard/orders- قائمة الطلبات/dashboard/profile- الملف الشخصي للمستخدم
استخدم Outlet لعرض المحتوى المتداخل وأنشئ شريطًا جانبيًا بـ NavLink للتنقل.
الخلاصة
في هذا الدرس، تعلمت أساسيات React Router الإصدار 6:
- إعداد
BrowserRouterوتعريف المسارات بـRoutesوRoute - التنقل بمكونات
LinkوNavLink - إنشاء مسارات متداخلة مع
Outlet - استخدام معاملات URL مع
useParams - تطبيق مسارات الفهرس للمحتوى الافتراضي
- بناء مسارات شاملة لصفحات 404
في الدرس التالي، سنستكشف تقنيات التوجيه المتقدمة بما في ذلك التنقل البرمجي والمسارات المحمية والتحميل الكسول.