Сохранение состояния в URL

В библиотеке Universal Router управление состоянием приложения через URL строится на основе маршрутов и их параметров. Каждый маршрут может содержать динамические сегменты, query-параметры и hash, что позволяет сохранять текущее состояние приложения прямо в адресной строке.

Динамические сегменты обозначаются двоеточием. Например:

const routes = [
  {
    path: '/users/:userId',
    action: ({ params }) => {
      return `Профиль пользователя с ID: ${params.userId}`;
    }
  }
];

Здесь :userId автоматически извлекается из URL, и его значение доступно в объекте params. Это позволяет хранить идентификаторы ресурсов, активные вкладки или другие ключевые состояния без необходимости держать их в глобальном хранилище.

Использование query-параметров

Query-параметры дают возможность сохранять более сложное состояние, включая фильтры, сортировку или пагинацию:

const routes = [
  {
    path: '/products',
    action: ({ query }) => {
      const { category, page } = query;
      return `Отображаем категорию: ${category}, страница: ${page}`;
    }
  }
];

В данном примере, при URL /products?category=books&page=2 объект query будет содержать { category: 'books', page: '2' }. Universal Router не занимается парсингом query самостоятельно, поэтому рекомендуется использовать сторонние библиотеки вроде qs или встроенный URLSearchParams для преобразования строки запроса в объект:

function parseQuery(search) {
  return Object.fromEntries(new URLSearchParams(search));
}

Сохранение состояния вкладок и фильтров

Очень удобно использовать URL для хранения активной вкладки или состояния фильтров. Это обеспечивает возможность прямого перехода по ссылке и восстановления состояния без перезагрузки приложения:

const routes = [
  {
    path: '/dashboard',
    action: ({ query }) => {
      const tab = query.tab || 'overview';
      return `Активная вкладка: ${tab}`;
    }
  }
];

Таким образом, URL /dashboard?tab=analytics будет открывать вкладку “analytics”, а /dashboard — вкладку по умолчанию.

Работа с history API

Для динамического изменения URL без перезагрузки страницы применяется history API: pushState, replaceState и событие popstate. Universal Router может быть интегрирован с этим API для синхронизации состояния маршрутов:

import UniversalRouter from 'universal-router';

const router = new UniversalRouter(routes);

function navigate(path) {
  history.pushState({}, '', path);
  router.resolve({ pathname: window.location.pathname, query: parseQuery(window.location.search) });
}

window.addEventListener('popstate', () => {
  router.resolve({ pathname: window.location.pathname, query: parseQuery(window.location.search) });
});

pushState позволяет добавлять новые записи в историю, а replaceState заменяет текущую, что полезно для обновления состояния фильтров без создания новой записи.

Динамические параметры и вложенные маршруты

В Universal Router параметры могут быть вложенными, что упрощает работу с комплексными URL:

const routes = [
  {
    path: '/users/:userId',
    children: [
      {
        path: '/posts/:postId',
        action: ({ params }) => {
          return `Пользователь ${params.userId}, пост ${params.postId}`;
        }
      }
    ]
  }
];

Для URL /users/42/posts/7 объект params будет содержать { userId: '42', postId: '7' }. Такой подход позволяет создавать глубоко вложенные интерфейсы с сохранением состояния каждого уровня.

Хранение состояния через hash

Hash-параметры (#) позволяют сохранять состояние без обращения к серверу. Universal Router может обрабатывать hash как отдельный путь:

const routes = [
  {
    path: '/settings',
    action: ({ hash }) => {
      const section = hash.replace('#', '') || 'general';
      return `Секция настроек: ${section}`;
    }
  }
];

URL /settings#privacy откроет секцию “privacy”, а /settings — секцию по умолчанию. Hash идеально подходит для реализации внутренних вкладок и скролл-позиций.

Комбинирование всех подходов

Для сложных интерфейсов удобно комбинировать динамические сегменты, query-параметры и hash:

const routes = [
  {
    path: '/projects/:projectId',
    children: [
      {
        path: '/tasks',
        action: ({ params, query, hash }) => {
          return `Проект ${params.projectId}, фильтр: ${query.status}, вкладка: ${hash.replace('#','')}`;
        }
      }
    ]
  }
];

URL /projects/15/tasks?status=open#details сохраняет идентификатор проекта, фильтр задач и активную вкладку. Это обеспечивает полное восстановление состояния интерфейса при обновлении страницы или переходе по ссылке.

Синхронизация состояния и локальное хранение

Для повышения стабильности состояния часто используют локальное хранение вместе с URL. Например, фильтры могут храниться в localStorage, а URL отражает только активное представление. При загрузке приложения URL синхронизируется с локальным хранилищем:

const filters = JSON.parse(localStorage.getItem('filters') || '{}');
const query = parseQuery(window.location.search);
const finalState = { ...filters, ...query };

Это позволяет поддерживать как долгосрочные настройки пользователя, так и мгновенные состояния интерфейса.

Вывод

Использование Universal Router совместно с динамическими параметрами, query и hash обеспечивает гибкую и детерминированную систему сохранения состояния через URL. Это облегчает навигацию, поддерживает прямые ссылки на конкретное состояние и упрощает интеграцию с frontend-фреймворками. Такой подход делает интерфейсы масштабируемыми и удобными для пользователя.