دليل شامل لـ React Router: بناء تطبيقات الصفحة الواحدة (SPA) وتوجيه المسارات بفعالية
إذا كنت قد بدأت للتو رحلتك مع React، فمن المحتمل أنك لا تزال تستوعب مفهوم تطبيقات الصفحة الواحدة (Single Page Application - SPA) بالكامل. تقليدياً، يعمل التوجيه (routing) كالتالي: عند كتابة مسار مثل /contact في شريط عنوان المتصفح، يقوم المتصفح بإرسال طلب GET إلى الخادم، والذي بدوره يعيد صفحة HTML كاملة كاستجابة. أما مع النموذج الجديد لتطبيقات الصفحة الواحدة، يتم التعامل مع جميع طلبات URL باستخدام الشيفرة البرمجية من جانب العميل (client-side code).
في سياق React، يعتبر كل صفحة مكوناً (component) من مكونات React. يقوم React Router بمطابقة عنوان URL وتحميل المكون الخاص بتلك الصفحة. تتم هذه العملية بسلاسة وسرعة فائقة، مما يمنح المستخدم تجربة شبيهة بالتطبيقات الأصلية (native app-like experience) داخل المتصفح، دون ظهور صفحات فارغة ومتقطعة بين انتقالات المسارات.
في هذا المقال، ستتعلم كيفية استخدام React Router ومكوناته الأساسية لإنشاء تطبيق صفحة واحدة متكامل. لذا، افتح محرر النصوص المفضل لديك، ودعنا نبدأ!
إعداد بيئة المشروع
إنشاء مشروع React جديد
ابدأ بإنشاء مشروع React جديد من خلال تنفيذ الأمر التالي في سطر الأوامر:
yarn create react-app react-router-demo
سنستخدم yarn لتثبيت التبعيات في هذا الدليل، ولكن يمكنك استخدام npm أيضاً.
تثبيت React Router DOM
الخطوة التالية هي تثبيت مكتبة react-router-dom، وهي حزمة التوجيه الأساسية لتطبيقات الويب في React:
yarn add react-router-dom
إضافة إطار عمل Bulma CSS
لتحسين مظهر المكونات، سنستخدم إطار عمل Bulma CSS. قم بإضافته إلى مشروعك:
yarn add bulma
تهيئة الملفات الأساسية
بعد تثبيت Bulma، قم باستيراد ملف bulma.min.css في ملف index.js، وقم بتنظيف أي شيفرة برمجية إضافية (boilerplate code) من ملف App.js.
import "bulma/css/bulma.min.css";
الآن بعد أن أصبح مشروعك جاهزاً، لنبدأ بإنشاء بعض مكونات الصفحات.
بناء مكونات الصفحات
قم بإنشاء مجلد باسم pages داخل مجلد src، حيث سنضع جميع مكونات الصفحات الخاصة بنا. لهذا العرض التوضيحي، سننشئ ثلاث صفحات: Home (الرئيسية)، About (حول)، و Profile (الملف الشخصي). الصق الشيفرة التالية داخل مكوني Home و About:
// pages/Home.js
import React from "react";
const Home = () => (
<div>
<h1 className="title is-1">This is the Home Page</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras gravida, risus at dapibus aliquet, elit quam scelerisque tortor, nec accumsan eros nulla interdum justo. Pellentesque dignissim, sapien et congue rutrum, lorem tortor dapibus turpis, sit amet vestibulum eros mi et odio.</p>
</div>
);
export default Home;
// pages/About.js
import React from "react";
const About = () => (
<div>
<h1 className="title is-1">This is the About Page</h1>
<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Duis consequat nulla ac ex consequat, in efficitur arcu congue. Nam fermentum commodo egestas.</p>
</div>
);
export default About;
سنقوم بإنشاء صفحة Profile لاحقاً في هذا المقال.
تصميم مكون شريط التنقل (Navbar)
لنبدأ بإنشاء شريط التنقل لتطبيقنا. سيستخدم هذا المكون مكون <NavLink /> من مكتبة react-router-dom. قم بإنشاء مجلد باسم components داخل مجلد src.
// components/Navbar.js
import React, { useState } from "react";
import { NavLink } from "react-router-dom";
const Navbar = () => {
const [isOpen, setOpen] = useState(false);
return (
<nav className="navbar is-primary" role="navigation" aria-label="main navigation">
<div className="container">
{/* ... */}
</div>
</nav>
);
};
export default Navbar;
سيتم استخدام متغير الحالة isOpen لتشغيل قائمة التنقل على الأجهزة المحمولة أو اللوحية. لنضف قائمة الهامبرغر (hamburger menu):
const Navbar = () => {
const [isOpen, setOpen] = useState(false);
return (
<nav className="navbar is-primary" role="navigation" aria-label="main navigation">
<div className="container">
<div className="navbar-brand">
<a
role="button"
className={`navbar-burger burger ${isOpen && "is-active"}`}
aria-label="menu"
aria-expanded="false"
onClick={() => setOpen(!isOpen)}
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
{/* ... */}
</div>
</nav>
);
};
إضافة روابط التنقل باستخدام NavLink
لإضافة الروابط في القائمة، استخدم مكون <NavLink /> من react-router-dom. يوفر مكون NavLink طريقة إعلانية (declarative way) للتنقل داخل التطبيق. إنه مشابه لمكون Link، إلا أنه يمكنه تطبيق نمط نشط (active style) على الرابط إذا كان نشطاً حالياً. لتحديد المسار الذي سيتم الانتقال إليه، استخدم الخاصية to ومرر اسم المسار. ستضيف الخاصية activeClassName فئة active إلى الرابط إذا كان نشطاً حالياً.
<NavLink className="navbar-item" activeClassName="is-active" to="/" exact>
Home
</NavLink>
في المتصفح، يتم عرض مكون NavLink كعلامة <a> مع قيمة سمة href التي تم تمريرها في الخاصية to. هنا، تحتاج أيضاً إلى تحديد الخاصية exact لضمان مطابقة دقيقة مع عنوان URL. أضف جميع الروابط وأكمل مكون Navbar:
import React, { useState } from "react";
import { NavLink } from "react-router-dom";
const Navbar = () => {
const [isOpen, setOpen] = useState(false);
return (
<nav className="navbar is-primary" role="navigation" aria-label="main navigation">
<div className="container">
<div className="navbar-brand">
<a
role="button"
className={`navbar-burger burger ${isOpen && "is-active"}`}
aria-label="menu"
aria-expanded="false"
onClick={() => setOpen(!isOpen)}
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div className={`navbar-menu ${isOpen && "is-active"}`}>
<div className="navbar-start">
<NavLink className="navbar-item" activeClassName="is-active" to="/" exact>
Home
</NavLink>
<NavLink className="navbar-item" activeClassName="is-active" to="/about">
About
</NavLink>
<NavLink className="navbar-item" activeClassName="is-active" to="/profile">
Profile
</NavLink>
</div>
<div className="navbar-end">
<div className="navbar-item">
<div className="buttons">
<a className="button is-white">Log in</a>
</div>
</div>
</div>
</div>
</div>
</nav>
);
};
export default Navbar;
إذا لاحظت، لقد أضفت زر تسجيل الدخول (Login). سنعود إلى مكون Navbar مرة أخرى عندما نناقش المسارات المحمية (protected routes) لاحقاً في هذا الدليل.
عرض الصفحات وإدارة المسارات
دمج شريط التنقل في التطبيق
الآن بعد أن تم إعداد مكون Navbar، لنضفه إلى الصفحة ونبدأ بعرض الصفحات. بما أن شريط التنقل هو مكون مشترك عبر جميع الصفحات، فبدلاً من استدعاء المكون في كل مكون صفحة على حدة، سيكون من الأفضل عرضه في تصميم مشترك (common layout).
// App.js
function App() {
return (
<>
<Navbar />
<div className="container mt-2" style={{ marginTop: 40 }}>
{/* Render the page here */}
</div>
</>
);
}
الآن، أضف مكونات الصفحات داخل الحاوية (container):
// App.js
function App() {
return (
<>
<Navbar />
<div className="container mt-2" style={{ marginTop: 40 }}>
<Home />
<About />
</div>
</>
);
}
إذا تحققت من النتائج الآن، ستلاحظ أن كلاً من مكوني Home و About يتم عرضهما على الصفحة. هذا لأننا لم نضف أي منطق توجيه بعد. تحتاج إلى استيراد مكون BrowserRouter من React Router لإضافة إمكانية توجيه المكونات. كل ما عليك فعله هو تغليف جميع مكونات الصفحات داخل مكون BrowserRouter. سيؤدي هذا إلى تمكين جميع مكونات الصفحات من الحصول على منطق التوجيه.
مقدمة إلى BrowserRouter و Route
ممتاز! ولكن مرة أخرى، لن يتغير شيء في النتائج – ستظل ترى الصفحتين معروضتين. تحتاج إلى عرض مكون الصفحة فقط إذا كان عنوان URL يطابق مساراً معيناً. هنا يأتي دور مكون Route من React Router. يحتوي مكون Route على خاصية path تقبل مسار الصفحة، ويجب تغليف مكون الصفحة بـ Route، كما هو موضح أدناه:
<Route path="/about">
<About />
</Route>
لنقم بنفس الشيء لمكون Home:
<Route exact path="/">
<Home />
</Route>
تخبر الخاصية exact أعلاه مكون Route بمطابقة المسار بدقة. إذا لم تضف الخاصية exact على المسار /، فستتطابق مع جميع المسارات التي تبدأ بـ /، بما في ذلك /about. إذا تحققت من النتائج الآن، فستظل ترى كلا المكونين معروضين. ولكن، إذا انتقلت إلى /about، ستلاحظ أن مكون About فقط هو الذي يتم عرضه.
استخدام Switch لضمان تطابق مسار واحد
ترى هذا السلوك لأن الموجه (router) يستمر في مطابقة عنوان URL مع المسارات حتى بعد أن يكون قد طابق مساراً بالفعل. نحتاج إلى إخبار الموجه بالتوقف عن المطابقة بمجرد أن يطابق مساراً واحداً. يتم ذلك باستخدام مكون Switch من React Router.
function App() {
return (
<BrowserRouter>
<Navbar />
<div className="container mt-2" style={{ marginTop: 40 }}>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/about">
<About />
</Route>
</Switch>
</div>
</BrowserRouter>
);
}
ها أنت ذا! لقد قمت بتكوين التوجيه بنجاح في تطبيق React الخاص بك.
المسارات المحمية وإعادة التوجيه (Redirect)
عند العمل على تطبيقات حقيقية، ستجد بعض المسارات خلف نظام مصادقة (authentication system). ستكون هناك مسارات أو صفحات لا يمكن الوصول إليها إلا بواسطة مستخدم مسجل الدخول (logged-in user). في هذا القسم، ستتعلم كيفية تنفيذ مثل هذه المسارات. يرجى ملاحظة أنني لن أقوم بإنشاء أي نموذج تسجيل دخول أو استخدام أي خدمة خلفية لمصادقة المستخدم. في تطبيق حقيقي، لن تقوم بتنفيذ المصادقة بالطريقة الموضحة هنا.
إنشاء صفحة الملف الشخصي (Profile)
لنقم بإنشاء مكون صفحة Profile الذي يجب أن يكون متاحاً فقط للمستخدم المصادق عليه.
// pages/Profile.js
import { useParams } from "react-router-dom";
const Profile = () => {
const { name } = useParams();
return (
<div>
<h1 className="title is-1">This is the Profile Page</h1>
<article className="message is-dark" style={{ marginTop: 40 }}>
<div className="message-header">
<p>{name}</p>
</div>
<div className="message-body">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.{" "}
<strong>Pellentesque risus mi</strong>, tempus quis placerat ut, porta nec nulla. Vestibulum rhoncus ac ex sit amet fringilla. Nullam gravida purus diam, et dictum <a>felis venenatis</a> efficitur. Aenean ac{" "}
<em>eleifend lacus</em>, in mollis lectus. Donec sodales, arcu et sollicitudin porttitor, tortor urna tempor ligula, id porttitor mi magna a neque. Donec dui urna, vehicula et sem eget, facilisis sodales sem.
</div>
</article>
</div>
);
};
سنحصل على اسم المستخدم من عنوان URL باستخدام معاملات المسار (route parameters). أضف مسار Profile إلى الموجه:
<Route path="/profile/:name">
<Profile />
</Route>
تطبيق منطق الحماية باستخدام HOC
حالياً، يمكن الوصول إلى صفحة الملف الشخصي مباشرة. لجعلها مساراً مصادقاً عليه، قم بإنشاء مكون ترتيب أعلى (Higher-Order Component - HOC) لتغليف منطق المصادقة.
const withAuth = (Component) => {
const AuthRoute = () => {
const isAuth = !!localStorage.getItem("token");
// ...
};
return AuthRoute;
};
لتحديد ما إذا كان المستخدم مصادقاً عليه أم لا، سنقوم بالحصول على رمز المصادقة (authentication token) المخزن في المتصفح عند تسجيل دخول المستخدم. إذا لم يكن المستخدم مصادقاً عليه، فسنقوم بإعادة توجيه المستخدم إلى الصفحة الرئيسية. يمكن استخدام مكون Redirect من React Router لإعادة توجيه المستخدم إلى مسار آخر.
const withAuth = (Component) => {
const AuthRoute = () => {
const isAuth = !!localStorage.getItem("token");
if (isAuth) {
return <Component />;
} else {
return <Redirect to="/" />;
}
};
return AuthRoute;
};
يمكنك أيضاً تمرير معلومات مستخدم أخرى مثل الاسم ومعرف المستخدم باستخدام الخاصيات (props) إلى المكون المغلف. بعد ذلك، استخدم withAuth HOC في مكون Profile.
import withAuth from "../components/withAuth";
const Profile = () => {
// ...
}
export default withAuth(Profile);
الآن، إذا حاولت زيارة /profile/JohnDoe، فسيتم إعادة توجيهك إلى الصفحة الرئيسية. هذا لأن رمز المصادقة لم يتم تعيينه بعد في تخزين المتصفح الخاص بك (browser storage).
إدارة تسجيل الدخول والخروج في Navbar
حسناً، لنعد إلى مكون Navbar ونضيف وظائف تسجيل الدخول والخروج. عندما يكون المستخدم مصادقاً عليه، اعرض زر “تسجيل الخروج” (Logout)، وعندما لا يكون المستخدم مسجلاً للدخول، اعرض زر “تسجيل الدخول” (Login).
// components/Navbar.js
const Navbar = () => {
// ...
return (
<nav className="navbar is-primary" role="navigation" aria-label="main navigation">
<div className="container">
{/* ... */}
<div className="navbar-end">
<div className="navbar-item">
<div className="buttons">
{!isAuth ? (
<button className="button is-white" onClick={loginUser}>
Log in
</button>
) : (
<button className="button is-black" onClick={logoutUser}>
Log out
</button>
)}
</div>
</div>
</div>
</div>
</nav>
);
}
إعادة التوجيه البرمجي باستخدام withRouter
عندما ينقر المستخدم على زر تسجيل الدخول، قم بتعيين رمز وهمي (dummy token) في التخزين المحلي (local storage)، وأعد توجيه المستخدم إلى صفحة الملف الشخصي. ولكن لا يمكننا استخدام مكون Redirect في هذه الحالة – نحتاج إلى إعادة توجيه المستخدم برمجياً (programmatically). عادةً ما يتم تخزين الرموز الحساسة المستخدمة للمصادقة في ملفات تعريف الارتباط (cookies) لأسباب أمنية.
يحتوي React Router على withRouter HOC الذي يقوم بحقن كائن history في خصائص المكون (props) للاستفادة من واجهة برمجة تطبيقات التاريخ (History API). كما أنه يمرر خصائص match و location المحدثة إلى المكون المغلف.
// components/Navbar.js
import { NavLink, withRouter } from "react-router-dom";
const Navbar = ({ history }) => {
const isAuth = !!localStorage.getItem("token");
const loginUser = () => {
localStorage.setItem("token", "some-login-token");
history.push("/profile/Vijit");
};
const logoutUser = () => {
localStorage.removeItem("token");
history.push("/");
};
return (
{ /* ... */ }
);
};
export default withRouter(Navbar);
وهكذا! لقد أصبحت الآن خبيراً في التعامل مع المسارات المحمية أيضاً. يمكنك الرجوع إلى الشيفرة الكاملة لهذا الدليل كمرجع لك.
الخلاصة التقنية
نأمل أن تكون قد اكتسبت الآن فهماً واضحاً لكيفية عمل توجيه العميل (client-side routing) بشكل عام، وكيفية تنفيذه في React باستخدام مكتبة React Router. في هذا الدليل، تعرفت على المكونات الحيوية في React Router مثل Route، Switch، NavLink، بالإضافة إلى مفاهيم متقدمة مثل المسارات المحمية (authenticated routes)، والتي تعد ضرورية لبناء تطبيقات ويب حديثة وفعالة. إن فهم هذه المكونات سيمكنك من إنشاء تجارب مستخدم سلسة وتفاعلية، مما يعزز أداء تطبيقاتك ويحسن من قابليتها للتوسع. لا تتردد في استكشاف وثائق React Router الرسمية للحصول على نظرة عامة أكثر تفصيلاً لكل مكون.