В библиотеке Universal Router управление состоянием приложения через URL строится на основе маршрутов и их параметров. Каждый маршрут может содержать динамические сегменты, query-параметры и hash, что позволяет сохранять текущее состояние приложения прямо в адресной строке.
Динамические сегменты обозначаются двоеточием. Например:
const routes = [
{
path: '/users/:userId',
action: ({ params }) => {
return `Профиль пользователя с ID: ${params.userId}`;
}
}
];
Здесь :userId автоматически извлекается из URL, и его
значение доступно в объекте params. Это позволяет хранить
идентификаторы ресурсов, активные вкладки или другие ключевые состояния
без необходимости держать их в глобальном хранилище.
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 — вкладку по
умолчанию.
Для динамического изменения 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-параметры (#) позволяют сохранять состояние без
обращения к серверу. 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-фреймворками. Такой подход делает интерфейсы масштабируемыми и удобными для пользователя.