Перейти к содержимому

17. Роутинг: @solidjs/router

Яша, роутинг в Solid — это @solidjs/router, и он здорово продуман! Поддержка вложенных маршрутов, data loaders, ленивой загрузки и даже route guards. Главное отличие от React Router — загрузчики данных (data()) интегрированы в роутер, а не вынесены в useEffect. Поехали разбираться! 🚀


Окно терминала
npm install @solidjs/router
main.tsx
import { render } from 'solid-js/web';
import { Router } from '@solidjs/router';
import App from './App';
render(
() => (
<Router>
<App />
</Router>
),
document.getElementById('root')!
);

import { Route, Router } from '@solidjs/router';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';
function App() {
return (
<Router>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="*404" component={NotFound} />
</Router>
);
}

import { A } from '@solidjs/router';
function Nav() {
return (
<nav>
{/* A автоматически добавляет класс active при совпадении маршрута */}
<A href="/" activeClass="active" end>Главная</A>
<A href="/about" activeClass="active">О нас</A>
<A href="/users" activeClass="active">Пользователи</A>
</nav>
);
}
// CSS
// a.active { color: #2c67d5; font-weight: bold; }

💡 end — точное совпадение. Без него / будет активен на всех страницах, так как /about тоже начинается с /.


import { useNavigate, useLocation } from '@solidjs/router';
function LoginForm() {
const navigate = useNavigate();
const location = useLocation();
const handleLogin = async (data: LoginData) => {
await login(data);
// Возвращаемся туда, откуда пришли (или на главную)
const from = location.query.from || '/dashboard';
navigate(from, { replace: true });
};
return (
<form onSubmit={handleLogin}>
{/* ... */}
</form>
);
}

import { useParams } from '@solidjs/router';
// Route: /users/:id
function UserProfile() {
const params = useParams<{ id: string }>();
// params.id — реактивный!
const [user] = createResource(
() => params.id, // источник (ключ кэша)
(id) => fetchUser(id)
);
return (
<Show when={user()} fallback={<p>Загрузка...</p>}>
{(u) => <h1>{u().name}</h1>}
</Show>
);
}

import { useSearchParams } from '@solidjs/router';
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams<{
q: string;
page: string;
category: string;
}>();
return (
<div>
<input
value={searchParams.q || ''}
onInput={(e) => setSearchParams({ q: e.target.value, page: '1' })}
placeholder="Поиск..."
/>
<select
value={searchParams.category || 'all'}
onChange={(e) => setSearchParams({ category: e.target.value })}
>
<option value="all">Все</option>
<option value="books">Книги</option>
<option value="movies">Фильмы</option>
</select>
<p>Поиск: {searchParams.q}, Страница: {searchParams.page || '1'}</p>
</div>
);
}

// Макет с общим меню
function DashboardLayout() {
return (
<div class="dashboard">
<aside>
<A href="/dashboard">Обзор</A>
<A href="/dashboard/profile">Профиль</A>
<A href="/dashboard/settings">Настройки</A>
</aside>
<main>
{/* Вложенные маршруты рендерятся здесь */}
<Outlet />
</main>
</div>
);
}
// Роутер
function App() {
return (
<Router>
<Route path="/" component={Home} />
<Route path="/dashboard" component={DashboardLayout}>
{/* Вложенные маршруты */}
<Route path="/" component={DashboardHome} />
<Route path="/profile" component={Profile} />
<Route path="/settings" component={Settings} />
</Route>
</Router>
);
}

import { lazy } from 'solid-js';
import { Route, Router } from '@solidjs/router';
// Компоненты загружаются только при переходе на маршрут
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Router>
<Suspense fallback={<LoadingSpinner />}>
<Route path="/" component={Home} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/settings" component={Settings} />
</Suspense>
</Router>
);
}

// Загрузчик данных — запускается ДО рендера компонента
import { createAsync, type RouteDefinition } from '@solidjs/router';
export const route = {
load: ({ params }) => getUserData(params.id), // предзагрузка
} satisfies RouteDefinition;
function UserPage() {
const params = useParams<{ id: string }>();
// createAsync использует данные из load() — уже кэшированы!
const user = createAsync(() => getUserData(params.id));
return (
<Suspense fallback={<Skeleton />}>
<h1>{user()?.name}</h1>
</Suspense>
);
}

import { Navigate } from '@solidjs/router';
function ProtectedRoute(props: { component: Component }) {
const [auth] = useContext(AuthContext);
return (
<Show
when={auth.isLoggedIn}
fallback={<Navigate href="/login?from=/dashboard" />}
>
<props.component />
</Show>
);
}
// Использование
<Route
path="/dashboard"
component={() => <ProtectedRoute component={Dashboard} />}
/>