React — SPA (Single Page Application). Браузер загружает страницу один раз, а затем React-приложение само управляет тем, что отображается, в зависимости от URL. Перехода между страницами с перезагрузкой нет — это быстрее и обеспечивает плавный пользовательский опыт.
React Router — наиболее популярная библиотека маршрутизации для React. Актуальная версия: React Router v6.
import { BrowserRouter, Routes, Route, Link, NavLink } from 'react-router-dom'
function App() {
return (
<BrowserRouter>
{/* Навигация */}
<nav>
<Link to="/">Главная</Link>
{/* NavLink добавляет класс "active" к активной ссылке */}
<NavLink to="/about" className={({ isActive }) => isActive ? 'active' : ''}>
О нас
</NavLink>
<Link to="/users">Пользователи</Link>
</nav>
{/* Маршруты — отображается только совпадающий */}
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/users" element={<UsersPage />} />
<Route path="/users/:id" element={<UserPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
)
}// URL: /users/42
function UserPage() {
const { id } = useParams() // { id: "42" } — всегда строка!
const userId = parseInt(id, 10) // конвертируем в число если нужно
const { data: user } = useFetch(`/api/users/${userId}`)
return <div>{user?.name}</div>
}function LoginForm() {
const navigate = useNavigate()
const handleSubmit = async (e) => {
e.preventDefault()
await login(credentials)
navigate('/dashboard') // переход вперёд
navigate(-1) // назад (как history.back())
navigate('/home', { replace: true }) // без добавления в историю
}
return <form onSubmit={handleSubmit}>...</form>
}// URL: /search?q=react&page=2
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams()
const query = searchParams.get('q') ?? ''
const page = parseInt(searchParams.get('page') ?? '1', 10)
const handleSearch = (q) => {
setSearchParams({ q, page: '1' }) // обновляет URL без перезагрузки
}
return <input value={query} onChange={e => handleSearch(e.target.value)} />
}function App() {
return (
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
{/* Дочерние маршруты рендерятся через <Outlet /> */}
<Route index element={<DashboardHome />} /> {/* /dashboard */}
<Route path="analytics" element={<Analytics />} /> {/* /dashboard/analytics */}
<Route path="settings" element={<Settings />} /> {/* /dashboard/settings */}
</Route>
</Routes>
)
}
function DashboardLayout() {
return (
<div>
<Sidebar />
<main>
<Outlet /> {/* Здесь рендерится активный дочерний маршрут */}
</main>
</div>
)
}function PrivateRoute({ children }) {
const { isAuthenticated } = useAuth()
if (!isAuthenticated) {
return <Navigate to="/login" replace />
}
return children
}
// Использование
<Route
path="/admin"
element={
<PrivateRoute>
<AdminPage />
</PrivateRoute>
}
/>Реализация hash-роутера с нуля: window.hashchange, рендер по текущему hash, навигация и параметры
// Реализуем простой роутер на основе window.location.hash.
// Это покажет, как клиентская маршрутизация работает без перезагрузки страницы.
// --- Создаём роутер ---
function createHashRouter(routes) {
// routes: { '/': handler, '/about': handler, '/users/:id': handler }
const listeners = new Set()
// Парсим шаблон маршрута: '/users/:id' -> регулярное выражение
function parseRoute(pattern) {
const params = []
const regexStr = pattern.replace(/:([^/]+)/g, (_, name) => {
params.push(name)
return '([^/]+)'
})
return { regex: new RegExp(`^${regexStr}$`), params }
}
// Находим подходящий маршрут и извлекаем параметры
function matchRoute(path) {
for (const [pattern, handler] of Object.entries(routes)) {
if (pattern === '*') continue // 404 обрабатываем последним
const { regex, params } = parseRoute(pattern)
const match = path.match(regex)
if (match) {
const paramValues = {}
params.forEach((name, i) => {
paramValues[name] = match[i + 1]
})
return { handler, params: paramValues }
}
}
// 404: если есть маршрут '*'
if (routes['*']) {
return { handler: routes['*'], params: {} }
}
return null
}
// Текущий path из hash
const getPath = () => {
const hash = window.location.hash.slice(1) // убираем '#'
return hash || '/'
}
// Рендер текущего маршрута
function render() {
const path = getPath()
const match = matchRoute(path)
console.log(`[Router] Навигация: ${path}`)
if (match) {
const result = match.handler({ params: match.params, path })
listeners.forEach(fn => fn({ path, params: match.params, view: result }))
}
}
// Слушаем изменения hash
window.addEventListener('hashchange', render)
return {
// Программная навигация
navigate(path) {
window.location.hash = path
// hashchange сработает автоматически
},
// Обратная навигация
back() {
window.history.back()
},
// Инициализация — рендерим текущий маршрут
start() {
render()
return this
},
// Подписка на навигацию (как useNavigate)
onNavigate(fn) {
listeners.add(fn)
return () => listeners.delete(fn)
},
destroy() {
window.removeEventListener('hashchange', render)
}
}
}
// --- Описываем маршруты ---
const router = createHashRouter({
'/': ({ path }) => {
console.log(' Рендер: Главная страница')
return 'Главная'
},
'/about': ({ path }) => {
console.log(' Рендер: О нас')
return 'О нас'
},
'/users': ({ path }) => {
console.log(' Рендер: Список пользователей')
return 'Пользователи'
},
'/users/:id': ({ params }) => {
console.log(` Рендер: Профиль пользователя #${params.id}`)
return `Пользователь ${params.id}`
},
'*': ({ path }) => {
console.log(` Рендер: 404 — страница "${path}" не найдена`)
return '404'
}
})
// Подписываемся на навигацию
router.onNavigate(({ path, params, view }) => {
console.log(' -> Текущий вид: "' + view + '", params:', params)
})
// --- Симуляция навигации ---
console.log('=== Запуск роутера ===')
// Эмулируем hashchange вручную (в браузере это делает window.location.hash = ...)
const simulateNav = (path) => {
Object.defineProperty(window, 'location', {
value: { hash: '#' + path, ...window.location },
writable: true
})
window.dispatchEvent(new HashChangeEvent('hashchange'))
}
// В браузере просто:
// router.navigate('/')
// router.navigate('/about')
// router.navigate('/users/42')
console.log('
В реальном браузере:')
console.log('router.navigate("/") -> рендер "Главная"')
console.log('router.navigate("/about") -> рендер "О нас"')
console.log('router.navigate("/users/42") -> params: { id: "42" }')
console.log('router.navigate("/unknown") -> рендер 404')
console.log('
React Router делает то же самое, но через History API (без #)')React — SPA (Single Page Application). Браузер загружает страницу один раз, а затем React-приложение само управляет тем, что отображается, в зависимости от URL. Перехода между страницами с перезагрузкой нет — это быстрее и обеспечивает плавный пользовательский опыт.
React Router — наиболее популярная библиотека маршрутизации для React. Актуальная версия: React Router v6.
import { BrowserRouter, Routes, Route, Link, NavLink } from 'react-router-dom'
function App() {
return (
<BrowserRouter>
{/* Навигация */}
<nav>
<Link to="/">Главная</Link>
{/* NavLink добавляет класс "active" к активной ссылке */}
<NavLink to="/about" className={({ isActive }) => isActive ? 'active' : ''}>
О нас
</NavLink>
<Link to="/users">Пользователи</Link>
</nav>
{/* Маршруты — отображается только совпадающий */}
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/users" element={<UsersPage />} />
<Route path="/users/:id" element={<UserPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
)
}// URL: /users/42
function UserPage() {
const { id } = useParams() // { id: "42" } — всегда строка!
const userId = parseInt(id, 10) // конвертируем в число если нужно
const { data: user } = useFetch(`/api/users/${userId}`)
return <div>{user?.name}</div>
}function LoginForm() {
const navigate = useNavigate()
const handleSubmit = async (e) => {
e.preventDefault()
await login(credentials)
navigate('/dashboard') // переход вперёд
navigate(-1) // назад (как history.back())
navigate('/home', { replace: true }) // без добавления в историю
}
return <form onSubmit={handleSubmit}>...</form>
}// URL: /search?q=react&page=2
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams()
const query = searchParams.get('q') ?? ''
const page = parseInt(searchParams.get('page') ?? '1', 10)
const handleSearch = (q) => {
setSearchParams({ q, page: '1' }) // обновляет URL без перезагрузки
}
return <input value={query} onChange={e => handleSearch(e.target.value)} />
}function App() {
return (
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
{/* Дочерние маршруты рендерятся через <Outlet /> */}
<Route index element={<DashboardHome />} /> {/* /dashboard */}
<Route path="analytics" element={<Analytics />} /> {/* /dashboard/analytics */}
<Route path="settings" element={<Settings />} /> {/* /dashboard/settings */}
</Route>
</Routes>
)
}
function DashboardLayout() {
return (
<div>
<Sidebar />
<main>
<Outlet /> {/* Здесь рендерится активный дочерний маршрут */}
</main>
</div>
)
}function PrivateRoute({ children }) {
const { isAuthenticated } = useAuth()
if (!isAuthenticated) {
return <Navigate to="/login" replace />
}
return children
}
// Использование
<Route
path="/admin"
element={
<PrivateRoute>
<AdminPage />
</PrivateRoute>
}
/>Реализация hash-роутера с нуля: window.hashchange, рендер по текущему hash, навигация и параметры
// Реализуем простой роутер на основе window.location.hash.
// Это покажет, как клиентская маршрутизация работает без перезагрузки страницы.
// --- Создаём роутер ---
function createHashRouter(routes) {
// routes: { '/': handler, '/about': handler, '/users/:id': handler }
const listeners = new Set()
// Парсим шаблон маршрута: '/users/:id' -> регулярное выражение
function parseRoute(pattern) {
const params = []
const regexStr = pattern.replace(/:([^/]+)/g, (_, name) => {
params.push(name)
return '([^/]+)'
})
return { regex: new RegExp(`^${regexStr}$`), params }
}
// Находим подходящий маршрут и извлекаем параметры
function matchRoute(path) {
for (const [pattern, handler] of Object.entries(routes)) {
if (pattern === '*') continue // 404 обрабатываем последним
const { regex, params } = parseRoute(pattern)
const match = path.match(regex)
if (match) {
const paramValues = {}
params.forEach((name, i) => {
paramValues[name] = match[i + 1]
})
return { handler, params: paramValues }
}
}
// 404: если есть маршрут '*'
if (routes['*']) {
return { handler: routes['*'], params: {} }
}
return null
}
// Текущий path из hash
const getPath = () => {
const hash = window.location.hash.slice(1) // убираем '#'
return hash || '/'
}
// Рендер текущего маршрута
function render() {
const path = getPath()
const match = matchRoute(path)
console.log(`[Router] Навигация: ${path}`)
if (match) {
const result = match.handler({ params: match.params, path })
listeners.forEach(fn => fn({ path, params: match.params, view: result }))
}
}
// Слушаем изменения hash
window.addEventListener('hashchange', render)
return {
// Программная навигация
navigate(path) {
window.location.hash = path
// hashchange сработает автоматически
},
// Обратная навигация
back() {
window.history.back()
},
// Инициализация — рендерим текущий маршрут
start() {
render()
return this
},
// Подписка на навигацию (как useNavigate)
onNavigate(fn) {
listeners.add(fn)
return () => listeners.delete(fn)
},
destroy() {
window.removeEventListener('hashchange', render)
}
}
}
// --- Описываем маршруты ---
const router = createHashRouter({
'/': ({ path }) => {
console.log(' Рендер: Главная страница')
return 'Главная'
},
'/about': ({ path }) => {
console.log(' Рендер: О нас')
return 'О нас'
},
'/users': ({ path }) => {
console.log(' Рендер: Список пользователей')
return 'Пользователи'
},
'/users/:id': ({ params }) => {
console.log(` Рендер: Профиль пользователя #${params.id}`)
return `Пользователь ${params.id}`
},
'*': ({ path }) => {
console.log(` Рендер: 404 — страница "${path}" не найдена`)
return '404'
}
})
// Подписываемся на навигацию
router.onNavigate(({ path, params, view }) => {
console.log(' -> Текущий вид: "' + view + '", params:', params)
})
// --- Симуляция навигации ---
console.log('=== Запуск роутера ===')
// Эмулируем hashchange вручную (в браузере это делает window.location.hash = ...)
const simulateNav = (path) => {
Object.defineProperty(window, 'location', {
value: { hash: '#' + path, ...window.location },
writable: true
})
window.dispatchEvent(new HashChangeEvent('hashchange'))
}
// В браузере просто:
// router.navigate('/')
// router.navigate('/about')
// router.navigate('/users/42')
console.log('
В реальном браузере:')
console.log('router.navigate("/") -> рендер "Главная"')
console.log('router.navigate("/about") -> рендер "О нас"')
console.log('router.navigate("/users/42") -> params: { id: "42" }')
console.log('router.navigate("/unknown") -> рендер 404')
console.log('
React Router делает то же самое, но через History API (без #)')Создай мини-роутер на основе useState. Компонент App хранит currentPage в state. В зависимости от currentPage рендерится один из трёх "экранов": Home, About или Users. Навигация осуществляется через кнопки (не настоящие ссылки). Активная кнопка подсвечивается другим цветом.
useState("home") хранит текущую страницу. В switch(currentPage) возвращай нужный компонент: <HomePage />, <AboutPage />, <UsersPage />. В навигации: onClick={() => setCurrentPage(item.id)}. Активную кнопку выделяй: currentPage === item.id.