ניתוב עם React Router
מבוא
React עצמה מטפלת ברכיב בודד, ולא בניווט בין דפים. כאן נכנסת React Router - ספריית הניתוב (Routing) הנפוצה ביותר לאפליקציות React. היא מאפשרת לבנות אפליקציית עמוד יחיד (SPA) שמתנהגת כאילו יש בה כמה דפים: הכתובת בשורת הדפדפן משתנה, אך במקום לטעון מחדש את כל העמוד, React Router פשוט מחליפה את הקומפוננטה המוצגת. התוצאה היא מעבר מהיר וחלק בין מסכים.
התקנה
npm install react-router-dom
הגדרה בסיסית
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
{/* תפריט ניווט */}
<nav>
<Link to="/">בית</Link>
<Link to="/about">אודות</Link>
<Link to="/contact">צור קשר</Link>
</nav>
{/* הגדרת נתיבים */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
function Home() {
return <h1>דף הבית</h1>;
}
function About() {
return <h1>אודות</h1>;
}
function Contact() {
return <h1>צור קשר</h1>;
}
function NotFound() {
return <h1>404 - הדף לא נמצא</h1>;
}
Link ו-NavLink
Link - קישור בסיסי
import { Link } from 'react-router-dom';
function Navigation() {
return (
<nav>
<Link to="/">בית</Link>
<Link to="/products">מוצרים</Link>
<Link to="/about">אודות</Link>
</nav>
);
}
NavLink - קישור עם סטייל פעיל
import { NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
<NavLink
to="/"
className={({ isActive }) => isActive ? 'active' : ''}
>
בית
</NavLink>
<NavLink
to="/products"
style={({ isActive }) => ({
fontWeight: isActive ? 'bold' : 'normal',
color: isActive ? 'blue' : 'black'
})}
>
מוצרים
</NavLink>
</nav>
);
}
Dynamic Routes (נתיבים דינמיים)
לעיתים קרובות נרצה נתיב שמשתנה לפי נתון - למשל עמוד פרופיל נפרד לכל משתמש. במקום להגדיר נתיב לכל אחד, מגדירים נתיב אחד עם פרמטר דינמי (המסומן בנקודתיים, למשל :userId), וקוראים את ערכו בעזרת ה-Hook בשם useParams:
הגדרת פרמטרים
function App() {
return (
<Routes>
<Route path="/users" element={<UserList />} />
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/products/:category/:productId"element={<Product />} />
</Routes>
);
}
קריאת פרמטרים עם useParams
import { useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams();
return <h1>פרופיל משתמש: {userId}</h1>;
}
function Product() {
const { category, productId } = useParams();
return (
<div>
<h1>קטגוריה: {category}</h1>
<h2>מוצר: {productId}</h2>
</div>
);
}
דוגמה מלאה
function UserList() {
const users = [
{ id: 1, name: 'דני' },
{ id: 2, name: 'רונית' },
{ id: 3, name: 'משה' }
];
return (
<div>
<h1>רשימת משתמשים</h1>
<ul>
{users.map(user => (
<li key={user.id}>
<Link to={`/users/${user.id}`}>{user.name}</Link>
</li>
))}
</ul>
</div>
);
}
function UserProfile() {
const { userId } = useParams();
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]);
if (!user) return <p>טוען...</p>;
return (
<div>
<h1>{user.name}</h1>
<p>אימייל: {user.email}</p>
</div>
);
}
Nested Routes (נתיבים מקוננים)
נתיבים מקוננים מאפשרים לשתף מבנה (Layout) בין כמה מסכים - למשל תפריט וכותרת קבועים, שבתוכם מתחלף רק התוכן. מגדירים נתיבי ילד בתוך נתיב הורה, וקומפוננטת ההורה מציינת בעזרת <Outlet /> היכן יוצג הנתיב הפעיל:
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="dashboard" element={<Dashboard />}>
<Route index element={<DashboardHome />} />
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Route>
</Routes>
);
}
Outlet - הצגת נתיב ילד
import { Outlet, Link } from 'react-router-dom';
function Layout() {
return (
<div>
<header>
<nav>
<Link to="/">בית</Link>
<Link to="/dashboard">Dashboard</Link>
</nav>
</header>
<main>
<Outlet /> {/* כאן יוצג הנתיב הילד */}
</main>
<footer>פוטר</footer>
</div>
);
}
function Dashboard() {
return (
<div className="dashboard">
<aside>
<nav>
<Link to="/dashboard">סקירה</Link>
<Link to="/dashboard/profile">פרופיל</Link>
<Link to="/dashboard/settings">הגדרות</Link>
</nav>
</aside>
<div className="content">
<Outlet /> {/* תתי-נתיבים */}
</div>
</div>
);
}
Query Parameters
פרמטרים של שאילתה (החלק שאחרי ה-? בכתובת, למשל ?q=react&page=2) מתאימים לנתונים אופציונליים כמו מונחי חיפוש, סינון ומספר עמוד. ה-Hook בשם useSearchParams מאפשר לקרוא ולעדכן אותם בדומה ל-useState:
import { useSearchParams } from 'react-router-dom';
function SearchResults() {
const [searchParams, setSearchParams] = useSearchParams();
// קריאה
const query = searchParams.get('q');
const page = searchParams.get('page') || '1';
const sort = searchParams.get('sort') || 'date';
// עדכון
const updateSearch = (newQuery) => {
setSearchParams({ q: newQuery, page: '1' });
};
const nextPage = () => {
setSearchParams({
q: query,
page: String(Number(page) + 1),
sort
});
};
return (
<div>
<input
value={query || ''}
onChange={(e) => updateSearch(e.target.value)}
placeholder="חפש..."
/>
<p>מציג תוצאות עבור: "{query}"</p>
<p>עמוד: {page}</p>
<button onClick={nextPage}>עמוד הבא</button>
</div>
);
}
Programmatic Navigation
לצד ניווט בלחיצה על קישור, לעיתים צריך לנווט מתוך הקוד עצמו - למשל להעביר את המשתמש ללוח הבקרה מיד לאחר התחברות מוצלחת. לשם כך משתמשים ב-Hook בשם useNavigate:
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
const success = await login(email, password);
if (success) {
// ניווט לאחר התחברות
navigate('/dashboard');
// או עם replace (לא נשמר בהיסטוריה)
navigate('/dashboard', { replace: true });
// או אחורה
navigate(-1);
}
};
return <form onSubmit={handleSubmit}>...</form>;
}
Protected Routes
נתיבים מוגנים חוסמים גישה למסכים מסוימים ממשתמשים שאינם מחוברים. מממשים זאת בעזרת קומפוננטת עטיפה שבודקת את מצב ההתחברות, ומפנה ל-login אם צריך - תוך שמירת היעד המקורי כדי לחזור אליו לאחר ההתחברות:
import { Navigate, useLocation } from 'react-router-dom';
function ProtectedRoute({ children }) {
const { isAuthenticated, loading } = useAuth();
const location = useLocation();
if (loading) {
return <p>טוען...</p>;
}
if (!isAuthenticated) {
// שמירת המיקום המבוקש לאחר התחברות
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// שימוש
function App() {
return (
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
);
}
// ניווט חזרה אחרי התחברות
function Login() {
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || '/';
const handleLogin = async () => {
await login();
navigate(from, { replace: true });
};
}
useLocation Hook
import { useLocation } from 'react-router-dom';
function CurrentPath() {
const location = useLocation();
// location.pathname = '/users/123'
// location.search = '?tab=profile'
// location.hash = '#section1'
// location.state = { from: ... }
return (
<div>
<p>נתיב: {location.pathname}</p>
<p>Query: {location.search}</p>
</div>
);
}
סיכום
| Component/Hook | שימוש |
|---|---|
BrowserRouter |
עוטף את האפליקציה |
Routes |
מכיל את הנתיבים |
Route |
הגדרת נתיב בודד |
Link |
קישור לניווט |
NavLink |
קישור עם active state |
Outlet |
הצגת nested route |
useParams |
קריאת פרמטרים דינמיים |
useSearchParams |
query string |
useNavigate |
ניווט בקוד |
useLocation |
מידע על הנתיב הנוכחי |
הקודם: ← React Patterns
הבא: Performance Optimization →
בדקו את עצמכם
נסו לענות לבד לפני שאתם פותחים את התשובה.
-
למה משתמשים ב-
<Link>ולא ב-<a href>בתוך האפליקציה?הצגת התשובה
תשובה ב. זה מה שהופך את האפליקציה ל-SPA.
-
איך קוראים פרמטר מכתובת כמו
/users/:id?הצגת התשובה
תשובה ד. useParams מחזיר את הפרמטרים מהנתיב.