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

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 | false
user.error // undefined | Error
user.state // 'unresolved' | 'pending' | 'ready' | 'refreshing' | 'errored'

Первый аргумент — 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>
);
}
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>
);
}
// 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>;
}
import { createServerResource$ } from 'solid-start/server';
// Выполняется ТОЛЬКО на сервере
const [serverData] = createResource(
() => ({ userId: props.userId }),
createServerResource$(async ({ userId }) => {
// Прямой доступ к базе данных — без HTTP-запросов
return db.users.findById(userId);
})
);