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

33. Экосистема Solid.js

Solid.js — не просто библиотека реактивности, это целая экосистема инструментов, примитивов и UI-компонентов. В этом уроке рассмотрим ключевые пакеты, которые используются в реальных проектах.

@solid-primitives — официальный монорепозиторий примитивов, поддерживаемый командой Solid. Каждый пакет решает одну задачу и устанавливается отдельно.

Окно терминала
# Примеры пакетов
npm install @solid-primitives/timer # таймеры, интервалы
npm install @solid-primitives/storage # localStorage, sessionStorage
npm install @solid-primitives/media # медиа-запросы
npm install @solid-primitives/fetch # fetch с кешированием
npm install @solid-primitives/keyboard # клавиатура
npm install @solid-primitives/clipboard # буфер обмена
npm install @solid-primitives/resize-observer # ResizeObserver
npm install @solid-primitives/scheduled # debounce, throttle
import { createLocalStorage } from '@solid-primitives/storage';
function Settings() {
const [settings, setSettings] = createLocalStorage('app-settings', {
theme: 'dark',
language: 'ru',
notifications: true,
});
return (
<div>
<select
value={settings.theme}
onChange={e => setSettings('theme', e.target.value)}
>
<option value="dark">Тёмная</option>
<option value="light">Светлая</option>
</select>
</div>
);
}
import { createDebounce, createThrottle } from '@solid-primitives/scheduled';
function SearchInput() {
const [query, setQuery] = createSignal('');
const debounced = createDebounce(
(value: string) => fetchSearchResults(value),
300
);
return (
<input
value={query()}
onInput={e => {
setQuery(e.target.value);
debounced(e.target.value);
}}
/>
);
}

TanStack Query (бывший React Query) полностью портирован на Solid.js с нативной реактивностью:

Окно терминала
npm install @tanstack/solid-query
import { QueryClient, QueryClientProvider, createQuery, createMutation } from '@tanstack/solid-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<UserList />
</QueryClientProvider>
);
}
function UserList() {
// createQuery — реактивный запрос
const usersQuery = createQuery(() => ({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
staleTime: 5 * 60 * 1000, // 5 минут
}));
// createMutation — мутация с оптимистичным обновлением
const createUser = createMutation(() => ({
mutationFn: (data: NewUser) => fetch('/api/users', {
method: 'POST',
body: JSON.stringify(data),
}).then(r => r.json()),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
}));
return (
<div>
<Switch>
<Match when={usersQuery.isPending}>
<p>Загрузка...</p>
</Match>
<Match when={usersQuery.isError}>
<p>Ошибка: {usersQuery.error.message}</p>
</Match>
<Match when={usersQuery.isSuccess}>
<For each={usersQuery.data}>
{user => <UserCard user={user} />}
</For>
</Match>
</Switch>
</div>
);
}

В React Query queryKey — просто массив. В Solid Query это функция-геттер, чтобы зависимости были реактивными:

// ✅ Solid Query — queryKey как функция, реактивен
const [userId, setUserId] = createSignal('123');
const userQuery = createQuery(() => ({
queryKey: ['user', userId()], // userId() читается реактивно
queryFn: () => fetchUser(userId()),
}));
// Когда userId() изменится — запрос автоматически повторится!

@kobalte/core — официальная библиотека headless-компонентов для Solid.js:

Окно терминала
npm install @kobalte/core
import { Dialog, Select, Accordion } from "@kobalte/core";
// Полностью доступный диалог без встроенных стилей
function MyDialog() {
return (
<Dialog.Root>
<Dialog.Trigger>Открыть</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Title>Заголовок</Dialog.Title>
<Dialog.Description>Описание</Dialog.Description>
<Dialog.CloseButton>×</Dialog.CloseButton>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
// Кастомный Select с поиском
function MySelect() {
const [value, setValue] = createSignal('apple');
return (
<Select.Root
value={value()}
onChange={setValue}
options={['apple', 'banana', 'cherry']}
placeholder="Выберите фрукт..."
itemComponent={props => (
<Select.Item item={props.item}>
<Select.ItemLabel>{props.item.rawValue}</Select.ItemLabel>
</Select.Item>
)}
>
<Select.Trigger>
<Select.Value<string>>
{state => state.selectedOption()}
</Select.Value>
</Select.Trigger>
<Select.Portal>
<Select.Content>
<Select.Listbox />
</Select.Content>
</Select.Portal>
</Select.Root>
);
}
Окно терминала
npm install @thisbeyond/solid-dnd
import {
DragDropProvider,
DragDropSensors,
DragOverlay,
SortableProvider,
createSortable,
closestCenter,
} from '@thisbeyond/solid-dnd';
function SortableItem(props: { item: string }) {
const sortable = createSortable(props.item);
return (
<div
use:sortable
class="sortable"
classList={{ 'opacity-25': sortable.isActiveDraggable }}
>
{props.item}
</div>
);
}
function SortableList() {
const [items, setItems] = createSignal(['A', 'B', 'C', 'D']);
const onDragEnd = ({ draggable, droppable }: DragEvent) => {
if (draggable && droppable) {
const fromIndex = items().indexOf(draggable.id as string);
const toIndex = items().indexOf(droppable.id as string);
const newItems = [...items()];
newItems.splice(toIndex, 0, newItems.splice(fromIndex, 1)[0]);
setItems(newItems);
}
};
return (
<DragDropProvider onDragEnd={onDragEnd} collisionDetector={closestCenter}>
<DragDropSensors>
<SortableProvider ids={items()}>
<For each={items()}>
{item => <SortableItem item={item} />}
</For>
</SortableProvider>
</DragDropSensors>
</DragDropProvider>
);
}
Окно терминала
npm install solid-icons
import { FiHome, FiUser, FiSettings } from 'solid-icons/fi'; // Feather
import { BiSolidStar } from 'solid-icons/bi'; // BoxIcons
import { AiOutlineGithub } from 'solid-icons/ai'; // Ant Design
function Nav() {
return (
<nav>
<FiHome size={24} color="white" />
<FiUser size={24} />
<FiSettings size={24} class="icon-btn" />
</nav>
);
}
Окно терминала
npm install @suid/material @suid/icons-material
import Button from '@suid/material/Button';
import TextField from '@suid/material/TextField';
import { createTheme, ThemeProvider } from '@suid/material/styles';
const theme = createTheme({
palette: {
primary: { main: '#2c67d5' },
},
});
function Form() {
return (
<ThemeProvider theme={theme}>
<TextField label="Имя" variant="outlined" />
<Button variant="contained" color="primary">
Отправить
</Button>
</ThemeProvider>
);
}

Витрина экосистемы — четыре популярных паттерна в одном демо: