← Назад к вопросам

Как работает маршрутизация?

1.7 Middle🔥 161 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает маршрутизация

Маршрутизация (Routing) - это основной механизм в современных веб-приложениях, который позволяет переходить между разными страницами без полной перезагрузки. Рассмотрю работу маршрутизации в различных контекстах.

1. Маршрутизация в браузере (History API)

Браузер предоставляет History API для управления историей переходов.

// История браузера
window.history.push('/page1'); // Перейти вперед
window.history.back(); // Вернуться назад
window.history.forward(); // Перейти вперед
window.history.go(-2); // На 2 страницы назад

// Более полезные методы
history.pushState(state, title, url);
history.replaceState(state, title, url);

// Пример
history.pushState(
  { page: 1 },
  'Users Page',
  '/users/1'
);

2. История событий (popstate)

Когда пользователь нажимает кнопку Back/Forward в браузере, срабатывает popstate событие.

window.addEventListener('popstate', (event) => {
  console.log('Пользователь нажал Back/Forward');
  console.log('State:', event.state);
  
  // Обновить UI на основе нового URL
  const path = window.location.pathname;
  renderPage(path);
});

// Пример с состоянием
function navigateTo(path, data) {
  history.pushState(data, '', path);
  renderPage(path);
}

navigatedTo('/users/123', { userId: 123 });

3. Маршрутизация в Next.js (App Router)

Next.js 13+ использует новый App Router с файловой структурой.

// app/page.js - главная страница ("/")
export default function Home() {
  return <h1>Главная</h1>;
}

// app/users/page.js - страница "/users"
export default function Users() {
  return <h1>Пользователи</h1>;
}

// app/users/[id]/page.js - динамический роут "/users/:id"
export default function UserDetail({ params }) {
  const { id } = params;
  return <h1>Пользователь {id}</h1>;
}

// app/users/[...slug]/page.js - catch-all роут
export default function CatchAll({ params }) {
  const { slug } = params; // массив сегментов
  return <div>Catch all: {slug.join('/')}</div>;
}

4. Клиентская навигация в Next.js

import { useRouter } from 'next/navigation';
import Link from 'next/link';

export function Navigation() {
  const router = useRouter();
  
  // Способ 1: Link компонент (рекомендуется)
  return (
    <nav>
      <Link href="/">Главная</Link>
      <Link href="/users">Пользователи</Link>
      <Link href="/users/123">Профиль</Link>
    </nav>
  );
}

export function UserAction() {
  const router = useRouter();
  
  // Способ 2: Программная навигация
  const handleClick = () => {
    // Перейти на новую страницу
    router.push('/users/123');
    
    // Заменить текущую запись в истории
    router.replace('/home');
    
    // Вернуться на предыдущую страницу
    router.back();
    
    // Перезагрузить текущую страницу
    router.refresh();
    
    // Перейти с префетчем
    router.prefetch('/expensive-page');
  };
  
  return <button onClick={handleClick}>Действие</button>;
}

5. Фильтрация и Query параметры

import { useSearchParams, useRouter } from 'next/navigation';

export function FilterUsers() {
  const router = useRouter();
  const searchParams = useSearchParams();
  
  // Получить query параметры
  const role = searchParams.get('role');
  const page = searchParams.get('page') || 1;
  
  const handleFilter = (newRole) => {
    // Создать новые параметры
    const params = new URLSearchParams(searchParams.toString());
    params.set('role', newRole);
    params.set('page', '1');
    
    // Обновить URL
    router.push(`?${params.toString()}`);
  };
  
  return (
    <div>
      <button onClick={() => handleFilter('admin')}>Admin</button>
      <button onClick={() => handleFilter('user')}>User</button>
      <p>Текущая роль: {role}</p>
      <p>Страница: {page}</p>
    </div>
  );
}

6. Матчинг маршрутов (Routing Matching)

Процесс определения, какой компонент показать для данного URL.

// Упрощенный алгоритм matcher
class Router {
  constructor() {
    this.routes = [];
  }
  
  addRoute(pattern, component) {
    this.routes.push({ pattern, component });
  }
  
  // Преобразовать паттерн в регулярное выражение
  patternToRegex(pattern) {
    const regex = pattern
      .replace(/\//g, '\\/')
      .replace(/:([a-zA-Z]+)/g, '(?<$1>[^/]+)')
      .replace(/\*\*/g, '.*');
    
    return new RegExp(`^${regex}$`);
  }
  
  // Найти маршрут для URL
  match(pathname) {
    for (let route of this.routes) {
      const regex = this.patternToRegex(route.pattern);
      const match = pathname.match(regex);
      
      if (match) {
        return {
          component: route.component,
          params: match.groups || {}
        };
      }
    }
    
    return null; // 404
  }
}

// Использование
const router = new Router();
router.addRoute('/', HomePage);
router.addRoute('/users', UsersPage);
router.addRoute('/users/:id', UserDetailPage);

const result = router.match('/users/123');
console.log(result);
// { component: UserDetailPage, params: { id: '123' } }

7. Создание кастомного маршрутизатора

class SimpleRouter {
  constructor() {
    this.routes = new Map();
    this.currentPath = window.location.pathname;
  }
  
  // Регистрация маршрута
  register(path, handler) {
    this.routes.set(path, handler);
  }
  
  // Выполнить обработчик для маршрута
  dispatch(path) {
    const handler = this.routes.get(path);
    if (handler) {
      handler();
      this.currentPath = path;
    } else {
      console.log('404: Route not found');
    }
  }
  
  // Перейти на маршрут
  navigate(path) {
    history.pushState({ path }, '', path);
    this.dispatch(path);
  }
  
  // Инициализировать слушатели
  init() {
    // При загрузке страницы
    this.dispatch(this.currentPath);
    
    // При нажатии Back/Forward
    window.addEventListener('popstate', (e) => {
      this.dispatch(window.location.pathname);
    });
  }
}

// Использование
const router = new SimpleRouter();

router.register('/', () => {
  document.body.innerHTML = '<h1>Главная</h1>';
});

router.register('/about', () => {
  document.body.innerHTML = '<h1>О сайте</h1>';
});

router.init();

// Навигация
router.navigate('/about');
router.navigate('/');

8. Охраняемые маршруты (Protected Routes)

// middleware.js (Next.js)
import { NextResponse } from 'next/server';

export function middleware(request) {
  const token = request.cookies.get('auth_token');
  const path = request.nextUrl.pathname;
  
  // Проверить, защищен ли маршрут
  const protectedPaths = ['/dashboard', '/profile', '/settings'];
  
  if (protectedPaths.some(p => path.startsWith(p))) {
    if (!token) {
      // Перенаправить на login
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*']
};

Ключевые концепции

  • Маршрутизация позволяет переходить между страницами без перезагрузки
  • History API управляет историей браузера
  • В Next.js маршруты основаны на файловой структуре
  • Query параметры позволяют передавать состояние через URL
  • Матчинг маршрутов - это процесс поиска нужного компонента
  • Охраняемые маршруты требуют проверки прав доступа