33. Экосистема Solid.js
Solid.js — не просто библиотека реактивности, это целая экосистема инструментов, примитивов и UI-компонентов. В этом уроке рассмотрим ключевые пакеты, которые используются в реальных проектах.
@solid-primitives — 100+ реактивных примитивов
Заголовок раздела «@solid-primitives — 100+ реактивных примитивов»@solid-primitives — официальный монорепозиторий примитивов, поддерживаемый командой Solid. Каждый пакет решает одну задачу и устанавливается отдельно.
# Примеры пакетовnpm install @solid-primitives/timer # таймеры, интервалыnpm install @solid-primitives/storage # localStorage, sessionStoragenpm 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 # ResizeObservernpm install @solid-primitives/scheduled # debounce, throttleПример: @solid-primitives/storage
Заголовок раздела «Пример: @solid-primitives/storage»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> );}Пример: @solid-primitives/scheduled
Заголовок раздела «Пример: @solid-primitives/scheduled»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 для Solid (@tanstack/solid-query)
Заголовок раздела «TanStack Query для Solid (@tanstack/solid-query)»TanStack Query (бывший React Query) полностью портирован на Solid.js с нативной реактивностью:
npm install @tanstack/solid-queryimport { 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
Заголовок раздела «Отличие от React Query»В 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 UI
Заголовок раздела «@kobalte/core — Headless UI»@kobalte/core — официальная библиотека headless-компонентов для Solid.js:
npm install @kobalte/coreimport { 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> );}solid-dnd — Drag and Drop
Заголовок раздела «solid-dnd — Drag and Drop»npm install @thisbeyond/solid-dndimport { 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> );}Solid Icons
Заголовок раздела «Solid Icons»npm install solid-iconsimport { FiHome, FiUser, FiSettings } from 'solid-icons/fi'; // Featherimport { BiSolidStar } from 'solid-icons/bi'; // BoxIconsimport { 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> );}SUID — Material UI для Solid
Заголовок раздела «SUID — Material UI для Solid»npm install @suid/material @suid/icons-materialimport 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> );}Интерактивный пример
Заголовок раздела «Интерактивный пример»Витрина экосистемы — четыре популярных паттерна в одном демо: