Маршрутизация
PreffX предоставляет лёгкий механизм клиентской маршрутизации на основе сигналов через утилиту url. Вам не нужно импортировать компонент маршрутизатора — вы получаете реактивный ReadonlySignal<URL>, который синхронизируется с местоположением браузера через Navigation API.
Сигнал url
Каждый компонент PreffX получает url во втором аргументе (utils).
| Свойство | Тип | Описание |
|---|---|---|
url | ReadonlySignal<URL> | Реактивный сигнал, чьё .value является текущим объектом URL. Изменяется при навигации пользователя на страницу того же источника. |
Сигнатура типа
type PreffXUtils = {
url: ReadonlySignal<URL>;
// ...
};Базовая маршрутизация
Используйте url.value для чтения текущего пути, параметров запроса, хеша или любого другого свойства URL. Комбинируйте с computed для реактивного переключения между представлениями.
import type { PC } from 'preffx';
const HomePage: PC = () => <div>Главная страница</div>;
const ContactsPage: PC = () => <div>Контакты</div>;
const NotFound: PC = () => <div>Страница не найдена</div>;
export const App: PC = (_, { computed, url }) => {
const page = computed(() => {
switch (url.value.pathname) {
case '/':
return <HomePage />;
case '/contacts':
return <ContactsPage />;
default:
return <NotFound />;
}
});
return (
<div>
<nav>
<a href="/">Главная</a>
<a href="/contacts">Контакты</a>
</nav>
<main>{page}</main>
</div>
);
};Как это работает
- Вы рендерите ссылки
<a href="/contacts">как обычно. - Когда пользователь нажимает на ссылку, браузер генерирует событие
navigate. - PreffX перехватывает событие (вызывает
event.preventDefault()) и обновляет внутренний сигналurl. - Любой
computed, зависящий отurl.value, пересчитывается, и DOM реактивно обновляется.
Навигационные ссылки
Используйте стандартные элементы <a> с href. PreffX обрабатывает перехват автоматически — не требуется специальный компонент <Link>.
<nav>
<a href="/">Главная</a>
<a href="/about">О нас</a>
<a href="/contacts">Контакты</a>
<a href="/products?category=books&sort=asc">Книги</a>
</nav>Перехватчик PreffX активируется только когда хотя бы один компонент использует сигнал url (отслеживается внутренним счётчиком URL_WATCHERS). Это означает:
- Если ни один компонент не читает
url, клики по ссылкам<a>работают обычным образом (полноценная навигация по странице). - Как только компонент обращается к
url.value, навигация перехватывается и обрабатывается на стороне клиента.
Чтение частей URL
Поскольку url является ReadonlySignal<URL>, вы можете читать любое свойство встроенного объекта URL:
const CurrentRoute: PC = (_, { url }) => {
return (
<div>
<p>Путь: {url.value.pathname}</p>
<p>Запрос: {url.value.search}</p>
<p>Хеш: {url.value.hash}</p>
<p>Хост: {url.value.host}</p>
<p>Источник: {url.value.origin}</p>
</div>
);
};Все эти свойства реактивны — изменения любого из них вызывают пересчёт зависимых сигналов.
Параметры маршрута (строки запроса)
Разбирайте параметры запроса из url.value.searchParams внутри computed:
import type { PC } from 'preffx';
const ProductList: PC = (_, { computed, url }) => {
const category = computed(() =>
url.value.searchParams.get('category') ?? 'все'
);
const sort = computed(() =>
url.value.searchParams.get('sort') ?? 'название'
);
return (
<div>
<p>Категория: {category}</p>
<p>Сортировка: {sort}</p>
<a href="/products?category=books&sort=asc">Книги (возр.)</a>
<a href="/products?category=books&sort=desc">Книги (убыв.)</a>
</div>
);
};Динамические сегменты (параметры пути)
Поскольку url предоставляет полный объект URL, вы можете разбирать сегменты пути вручную:
import type { PC } from 'preffx';
const UserProfile: PC = (_, { computed, url }) => {
const segments = computed(() => url.value.pathname.split('/').filter(Boolean));
// pathname = "/users/42" → segments = ["users", "42"]
const userId = computed(() => segments.value[1]);
return <div>ID пользователя: {userId}</div>;
};Для более структурированного подхода создайте простой сопоставитель маршрутов:
import type { PC } from 'preffx';
// Простой шаблон маршрута → извлечение параметров
function match(pattern: string, pathname: string): Record<string, string> | null {
const patternParts = pattern.split('/').filter(Boolean);
const pathParts = pathname.split('/').filter(Boolean);
if (patternParts.length !== pathParts.length) return null;
const params: Record<string, string> = {};
for (let i = 0; i < patternParts.length; i++) {
if (patternParts[i].startsWith(':')) {
params[patternParts[i].slice(1)] = pathParts[i];
} else if (patternParts[i] !== pathParts[i]) {
return null;
}
}
return params;
}
const RouteExample: PC = (_, { computed, url }) => {
const route = computed(() => {
const { pathname } = url.value;
const params = match('/users/:id', pathname);
if (params) return <div>Пользователь {params.id}</div>;
const postParams = match('/posts/:postId', pathname);
if (postParams) return <div>Пост {postParams.postId}</div>;
return <div>Не найдено</div>;
});
return (
<div>
<nav>
<a href="/users/42">Пользователь 42</a>
<a href="/users/7">Пользователь 7</a>
<a href="/posts/abc">Пост abc</a>
</nav>
<main>{route}</main>
</div>
);
};Междоменная навигация
Перехватчик навигации применяется только к навигациям внутри одного источника. Междоменные ссылки (другой протокол, хост или порт) остаются нетронутыми и вызывают полную загрузку страницы как обычно.
<a href="https://example.com">Внешняя ссылка</a> <!-- полная навигация -->
<a href="/internal">Внутренняя ссылка</a> <!-- клиентская навигация -->Поддержка браузеров
Маршрутизация основана на Navigation API (globalThis.navigation).
Начиная с января 2026 года, эта функция работает на новейших устройствах и версиях браузеров. Эта функция может не работать на старых устройствах или в старых браузерах.