React

React Router — التنقل بين الصفحات في React 2026

📅 2026-10-17⏱ 9 دقائق قراءة
في المقالات السابقة، بنينا تطبيقات React **بصفحة واحدة**. لكن التطبيقات الحقيقية تحتاج **صفحات متعددة**. هنا يأتي دور **React Router**. في هذا الدليل العملي، سنأخذك خطوة بخطوة لتعلم React Router وكيفية بناء تطبيق متعدد الصفحات، مع تمارين وحلول. ## ما هو React Router؟ **React Router** هي **مكتبة** تُضيف التنقل (Routing) لتطبيقات React. تسمح لك بـ: - **تبديل الصفحات** بدون إعادة تحميل. - **تمرير معاملات** في الرابط. - **حماية المسارات** (Auth). - **التنقل المتداخل** (Nested Routes). **تشبيه بسيط:** تطبيق React **الواحد** يمكن أن يحتوي على **عدة صفحات**. React Router هو **الموجّه** الذي يعرض الصفحة الصحيحة بناءً على الرابط. ## لماذا نحتاج React Router؟ **بدون React Router:** - ❌ إما صفحة واحدة فقط. - ❌ أو إعادة تحميل الصفحة لكل تنقل. **مع React Router:** - ✅ صفحات متعددة. - ✅ تنقل فوري (SPA). - ✅ روابط قابلة للمشاركة. - ✅ زر الرجوع يعمل. ## أنواع المكتبات هناك **مكتبتان** رئيسيتان: | المكتبة | الوصف | الاستخدام | |---------|-------|-----------| | **React Router** | المكتبة الكلاسيكية | للـ SPA | | **TanStack Router** | مكتبة حديثة | Type-safe | **سنستخدم React Router** لأنها الأكثر شعبية. ## تثبيت React Router في Terminal، اذهب إلى مشروعك، واكتب: ```bash npm install react-router-dom ``` **النتيجة:** ستُضاف المكتبة إلى `package.json`. ## الإعداد الأساسي ### 1. في `src/main.tsx` ```tsx import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; import App from "./App"; import "./index.css"; createRoot(document.getElementById("root")!).render( <StrictMode> <BrowserRouter> <App /> </BrowserRouter> </StrictMode> ); ``` **شرح:** `BrowserRouter` يُغلّف التطبيق كله ليُتيح التنقل. ### 2. في `src/App.tsx` ```tsx import { Routes, Route } from "react-router-dom"; import Home from "./pages/Home"; import About from "./pages/About"; import NotFound from "./pages/NotFound"; function App() { return ( <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> <Route path="*" element={<NotFound />} /> </Routes> ); } export default App; ``` **شرح:** - **`Routes`:** حاوية لكل المسارات. - **`Route`:** مسار واحد (path + element). - **`path="*"`:** مسار افتراضي (404). ### 3. إنشاء الصفحات **أنشئ `src/pages/Home.tsx`:** ```tsx function Home() { return ( <div> <h1>🏠 الصفحة الرئيسية</h1> <p>مرحباً بك في موقعنا!</p> </div> ); } export default Home; ``` **أنشئ `src/pages/About.tsx`:** ```tsx function About() { return ( <div> <h1>ℹ️ من نحن</h1> <p>نحن فريق عربي متخصص في التقنية.</p> </div> ); } export default About; ``` **أنشئ `src/pages/NotFound.tsx`:** ```tsx import { Link } from "react-router-dom"; function NotFound() { return ( <div style={{ textAlign: "center", padding: 40 }}> <h1>404</h1> <p>الصفحة غير موجودة</p> <Link to="/">العودة للرئيسية</Link> </div> ); } export default NotFound; ``` ## التنقل بين الصفحات ### 1. باستخدام `<Link>` ```tsx import { Link } from "react-router-dom"; function Navbar() { return ( <nav> <Link to="/">الرئيسية</Link> <Link to="/about">من نحن</Link> <Link to="/contact">اتصل بنا</Link> </nav> ); } ``` **الفرق عن `<a>`:** - `<a>`: يعيد تحميل الصفحة. - `<Link>`: تنقل فوري (SPA). ### 2. باستخدام `useNavigate` ```tsx import { useNavigate } from "react-router-dom"; function LoginButton() { const navigate = useNavigate(); const handleClick = () => { // ... منطق تسجيل الدخول navigate("/dashboard"); }; return <button onClick={handleClick}>تسجيل الدخول</button>; } ``` ### 3. باستخدام `NavLink` (للروابط النشطة) ```tsx import { NavLink } from "react-router-dom"; function Navbar() { return ( <nav> <NavLink to="/" className={({ isActive }) => isActive ? "active" : ""}> الرئيسية </NavLink> <NavLink to="/about" className={({ isActive }) => isActive ? "active" : ""}> من نحن </NavLink> </nav> ); } ``` **الفائدة:** `NavLink` يعرف متى يكون الرابط **نشطاً** (الصفحة الحالية). ## المسارات الديناميكية ### 1. تعريف المسار ```tsx <Route path="/user/:id" element={<UserProfile />} /> ``` **`:id`** هو **معامل ديناميكي**. ### 2. قراءة المعامل ```tsx import { useParams } from "react-router-dom"; function UserProfile() { const { id } = useParams<{ id: string }>(); return ( <div> <h1>الملف الشخصي للمستخدم #{id}</h1> </div> ); } ``` **النتيجة:** - `/user/1` → "الملف الشخصي للمستخدم #1" - `/user/42` → "الملف الشخصي للمستخدم #42" ## المسارات المتداخلة (Nested Routes) ```tsx <Routes> <Route path="/" element={<Layout />}> <Route index element={<Home />} /> <Route path="about" element={<About />} /> <Route path="contact" element={<Contact />} /> </Route> </Routes> ``` **`Layout.tsx`:** ```tsx import { Outlet } from "react-router-dom"; import Navbar from "./Navbar"; function Layout() { return ( <div> <Navbar /> <main> <Outlet /> {/* ← الصفحة الحالية تُعرض هنا */} </main> <footer>© 2026</footer> </div> ); } ``` **الفائدة:** Navbar و Footer يظهران في **كل الصفحات**. ## حماية المسارات (Protected Routes) ```tsx import { Navigate } from "react-router-dom"; function ProtectedRoute({ children }: { children: React.ReactNode }) { const isLoggedIn = localStorage.getItem("token"); if (!isLoggedIn) { return <Navigate to="/login" replace />; } return <>{children}</>; } // الاستخدام: <Route path="/dashboard" element={ <ProtectedRoute> <Dashboard /> </ProtectedRoute> } /> ``` ## مثال عملي: مدونة كاملة ### 1. الصفحات **`src/pages/Home.tsx`:** ```tsx import { Link } from "react-router-dom"; function Home() { return ( <div> <h1>📝 مدونتي</h1> <Link to="/posts">تصفح المقالات</Link> </div> ); } export default Home; ``` **`src/pages/Posts.tsx`:** ```tsx import { Link } from "react-router-dom"; const posts = [ { id: 1, title: "تعلم React", excerpt: "..." }, { id: 2, title: "تعلم TypeScript", excerpt: "..." }, { id: 3, title: "بناء API", excerpt: "..." }, ]; function Posts() { return ( <div> <h1>📚 المقالات</h1> <ul> {posts.map((post) => ( <li key={post.id}> <Link to={`/posts/${post.id}`}>{post.title}</Link> </li> ))} </ul> </div> ); } export default Posts; ``` **`src/pages/PostDetail.tsx`:** ```tsx import { useParams, Link } from "react-router-dom"; const posts = { "1": { title: "تعلم React", content: "React مكتبة رائعة..." }, "2": { title: "تعلم TypeScript", content: "TypeScript تضيف أنواع..." }, "3": { title: "بناء API", content: "Node.js مع Express..." }, }; function PostDetail() { const { id } = useParams<{ id: string }>(); const post = posts[id as keyof typeof posts]; if (!post) { return ( <div> <p>المقال غير موجود</p> <Link to="/posts">العودة</Link> </div> ); } return ( <article> <h1>{post.title}</h1> <p>{post.content}</p> <Link to="/posts">← العودة للمقالات</Link> </article> ); } export default PostDetail; ``` ### 2. `App.tsx` ```tsx import { Routes, Route } from "react-router-dom"; import Layout from "./components/Layout"; import Home from "./pages/Home"; import Posts from "./pages/Posts"; import PostDetail from "./pages/PostDetail"; import NotFound from "./pages/NotFound"; function App() { return ( <Routes> <Route path="/" element={<Layout />}> <Route index element={<Home />} /> <Route path="posts" element={<Posts />} /> <Route path="posts/:id" element={<PostDetail />} /> <Route path="*" element={<NotFound />} /> </Route> </Routes> ); } export default App; ``` ## الاستعلامات (Query Parameters) **الرابط:** ``` /posts?sort=date&filter=recent ``` **القراءة:** ```tsx import { useSearchParams } from "react-router-dom"; function Posts() { const [searchParams, setSearchParams] = useSearchParams(); const sort = searchParams.get("sort"); const filter = searchParams.get("filter"); return ( <div> <p>الترتيب: {sort}</p> <p>التصفية: {filter}</p> <button onClick={() => setSearchParams({ sort: "title" })}> ترتيب حسب العنوان </button> </div> ); } ``` ## تمارين عملية ### تمرين 1: موقع بسيط أنشئ موقعاً بصفحتين: Home و About. **الحل:** ```tsx <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> ``` ### تمرين 2: Navbar مع Link أنشئ Navbar بروابط للصفحات. **الحل:** ```tsx function Navbar() { return ( <nav> <Link to="/">الرئيسية</Link> <Link to="/about">من نحن</Link> <Link to="/contact">اتصل بنا</Link> </nav> ); } ``` ### تمرين 3: مسار ديناميكي أنشئ صفحة `/user/:id` تعرض رقم المستخدم. **الحل:** ```tsx function User() { const { id } = useParams(); return <h1>المستخدم #{id}</h1>; } ``` ### تمرين 4: 404 أنشئ صفحة 404 مع رابط للعودة. **الحل:** ```tsx function NotFound() { return ( <div> <h1>404</h1> <Link to="/">العودة للرئيسية</Link> </div> ); } ``` ### تمرين 5: التنقل برمجياً أنشئ زر ينقلك لصفحة أخرى. **الحل:** ```tsx function GoButton() { const navigate = useNavigate(); return <button onClick={() => navigate("/about")}>اذهب لمن نحن</button>; } ``` ### تمرين 6: NavLink نشط أضف تنسيقاً للرابط النشط. **الحل:** ```tsx <NavLink to="/about" className={({ isActive }) => isActive ? "active" : ""}> من نحن </NavLink> ``` ### تمرين 7: مسارات متداخلة أنشئ Layout مع Navbar و Footer. **الحل:** ```tsx function Layout() { return ( <> <Navbar /> <main><Outlet /></main> <footer>© 2026</footer> </> ); } ``` ### تمرين 8: حماية مسار أنشئ مسار `/dashboard` محمي. **الحل:** ```tsx function ProtectedRoute({ children }: { children: React.ReactNode }) { const isLoggedIn = !!localStorage.getItem("token"); return isLoggedIn ? <>{children}</> : <Navigate to="/login" />; } ``` ## حل المشاكل الشائعة ### 🔴 المشكلة 1: `useNavigate() may be used only in the context of a <Router>` **السبب:** لم تُغلّف التطبيق بـ `BrowserRouter`. **الحل:** أضف `<BrowserRouter>` في `main.tsx`. ### 🔴 المشكلة 2: الصفحة تُحدّث عند التنقل **السبب:** استخدام `<a href>` بدلاً من `<Link>`. **الحل:** استخدم `<Link to="...">`. ### 🔴 المشكلة 3: `useParams` لا يعمل **السبب:** المسار غير ديناميكي. **الحل:** تأكد من تعريف `:id` في المسار. ### 🔴 المشكلة 4: 404 في الإنتاج **السبب:** السيرفر لا يعرف كيف يتعامل مع المسارات. **الحل (Firebase):** أضف `rewrites` في `firebase.json`: ```json "rewrites": [ { "source": "**", "destination": "/index.html" } ] ``` **⚠️ مهم جداً:** بدون هذا، المسارات المباشرة (مثل `/about`) لن تعمل في الإنتاج! ## جدول مكونات React Router <table> <thead> <tr> <th>المكوّن/الدالة</th> <th>الوظيفة</th> </tr> </thead> <tbody> <tr> <td><code>BrowserRouter</code></td> <td>يُغلّف التطبيق</td> </tr> <tr> <td><code>Routes</code></td> <td>حاوية المسارات</td> </tr> <tr> <td><code>Route</code></td> <td>مسار واحد</td> </tr> <tr> <td><code>Link</code></td> <td>رابط تنقل</td> </tr> <tr> <td><code>NavLink</code></td> <td>رابط مع حالة نشطة</td> </tr> <tr> <td><code>Outlet</code></td> <td>مكان عرض الصفحات الفرعية</td> </tr> <tr> <td><code>Navigate</code></td> <td>إعادة توجيه</td> </tr> <tr> <td><code>useNavigate</code></td> <td>دالة التنقل</td> </tr> <tr> <td><code>useParams</code></td> <td>قراءة معاملات المسار</td> </tr> <tr> <td><code>useSearchParams</code></td> <td>قراءة query params</td> </tr> </tbody> </table> ## قائمة تحقق نهائية <table> <thead> <tr> <th>المهمة</th> <th>الحالة</th> </tr> </thead> <tbody> <tr> <td>تثبيت React Router</td> <td>⬜</td> </tr> <tr> <td>إعداد BrowserRouter</td> <td>⬜</td> </tr> <tr> <td>إنشاء Routes و Route</td> <td>⬜</td> </tr> <tr> <td>استخدام Link و NavLink</td> <td>⬜</td> </tr> <tr> <td>المسارات الديناميكية</td> <td>⬜</td> </tr> <tr> <td>المسارات المتداخلة</td> <td>⬜</td> </tr> <tr> <td>حماية المسارات</td> <td>⬜</td> </tr> <tr> <td>حل التمارين الثمانية</td> <td>⬜</td> </tr> </tbody> </table> ## ⚠️ مهم جداً: إعداد Firebase لتعمل المسارات في الإنتاج على Firebase، **تأكد** من أن `firebase.json` يحتوي على: ```json "rewrites": [ { "source": "**", "destination": "/index.html" } ] ``` **بدون هذا، ستظهر صفحة 404 عند زيارة أي مسار مباشر.** ## ماذا بعد هذا المقال؟ الآن بعد أن أتقنت React Router، أنت جاهز للمقال التالي: 1. **Context API** — إدارة الحالة العامة. 2. **Redux Toolkit** — إدارة حالة متقدمة. 3. **مشروع متكامل** — تطبيق كامل. ## الخلاصة في هذا المقال، تعلمت: - ✅ ما هو React Router ولماذا نحتاجه. - ✅ تثبيت وإعداد React Router. - ✅ Routes و Route. - ✅ Link, NavLink, useNavigate. - ✅ المسارات الديناميكية (useParams). - ✅ المسارات المتداخلة (Outlet). - ✅ حماية المسارات (Protected Routes). - ✅ حل 8 تمارين عملية. **تذكر:** React Router يجعل تطبيقك **SPA حقيقي**. أتقنه جيداً. **هل أتقنت React Router؟ شاركنا في التعليقات!**