12. Resources: createResource и async
createResource — встроенный примитив Solid для работы с асинхронными данными. Он связывает сигнал-источник с async-функцией и автоматически управляет состояниями загрузки, данных и ошибок.
Базовое использование
Заголовок раздела «Базовое использование»import { createResource } from 'solid-js';
// Простая загрузка данныхconst [user] = createResource(async () => { const res = await fetch('/api/user/1'); return res.json() as Promise<User>;});
// Состояния ресурса:user() // undefined (загружается) | User (готово)user.loading // true | falseuser.error // undefined | Erroruser.state // 'unresolved' | 'pending' | 'ready' | 'refreshing' | 'errored'Source сигнал — реактивные запросы
Заголовок раздела «Source сигнал — реактивные запросы»Первый аргумент — source: если он изменится, ресурс автоматически перезапустится:
import { createSignal, createResource } from 'solid-js';
function UserProfile() { const [userId, setUserId] = createSignal(1);
// userId() — source: при смене userId запрос повторится const [user] = createResource(userId, async (id) => { const res = await fetch(\`/api/users/\${id}\`); if (!res.ok) throw new Error(\`HTTP \${res.status}\`); return res.json() as Promise<User>; });
return ( <div> <Show when={user.loading}> <Spinner /> </Show> <Show when={user.error}> <ErrorMessage message={user.error.message} /> </Show> <Show when={user()}> {(u) => <UserCard user={u()} />} </Show> <button onClick={() => setUserId(id => id + 1)}>Следующий</button> </div> );}Параметры source — объект для сложных запросов
Заголовок раздела «Параметры source — объект для сложных запросов»function SearchResults() { const [query, setQuery] = createSignal(''); const [page, setPage] = createSignal(1);
// Источник — объект. Ресурс обновится при изменении query или page const [results] = createResource( () => ({ q: query(), page: page() }), async ({ q, page }) => { if (!q.trim()) return { items: [], total: 0 }; const res = await fetch(\`/api/search?q=\${q}&page=\${page}\`); return res.json(); } );
return ( <div> <input value={query()} onInput={e => { setQuery(e.target.value); setPage(1); // Сброс страницы при новом запросе }} /> <Suspense fallback={<Skeleton />}> <For each={results()?.items}> {item => <ResultCard item={item} />} </For> </Suspense> </div> );}refetch — ручное обновление
Заголовок раздела «refetch — ручное обновление»function Dashboard() { const [data, { refetch, mutate }] = createResource(fetchDashboardData);
// refetch() — повторяет запрос // mutate(newValue) — оптимистично обновляет значение без запроса
const handleRefresh = () => refetch();
const handleOptimisticUpdate = () => { // Обновляем UI сразу, без ожидания сервера mutate(prev => prev ? { ...prev, count: prev.count + 1 } : prev); // Затем синхронизируемся с сервером fetch('/api/increment', { method: 'POST' }).catch(() => refetch()); };
return ( <div> <button onClick={handleRefresh} disabled={data.loading}> {data.loading ? 'Обновление...' : '🔄 Обновить'} </button> <Show when={data()}>{d => <Stats data={d()} />}</Show> </div> );}Состояния ресурса
Заголовок раздела «Состояния ресурса»const [resource] = createResource(source, fetcher);
// Полная обработка всех состоянийfunction ResourceView() { return ( <Switch> <Match when={resource.state === 'pending'}> <LoadingSpinner /> </Match> <Match when={resource.state === 'errored'}> <ErrorBoundary error={resource.error} /> </Match> <Match when={resource.state === 'ready' || resource.state === 'refreshing'}> <div class={resource.state === 'refreshing' ? 'opacity-50' : ''}> <DataView data={resource()} /> {resource.state === 'refreshing' && <RefreshIndicator />} </div> </Match> </Switch> );}Интеграция с Suspense
Заголовок раздела «Интеграция с Suspense»// createResource автоматически интегрируется с <Suspense>function App() { return ( <Suspense fallback={<PageSkeleton />}> {/* UserProfile "приостановит" рендер до загрузки */} <UserProfile /> </Suspense> );}
function UserProfile() { // При использовании внутри Suspense — не нужно проверять loading вручную const [user] = createResource(fetchUser); // user() будет undefined только до первой загрузки, // затем Suspense заменит компонент fallback'ом return <div>{user()!.name}</div>;}Серверные ресурсы (SolidStart)
Заголовок раздела «Серверные ресурсы (SolidStart)»import { createServerResource$ } from 'solid-start/server';
// Выполняется ТОЛЬКО на сервереconst [serverData] = createResource( () => ({ userId: props.userId }), createServerResource$(async ({ userId }) => { // Прямой доступ к базе данных — без HTTP-запросов return db.users.findById(userId); }));