Suspense і Data fetching
1. Що робить Suspense при завантаженні даних
Suspenseдозволяє React призупиняти рендеринг частини дерева, поки компонент не отримає потрібні дані, і показувати fallback-UI (наприклад, спінер чи скелетон).
Тобто тепер React може "чекати" не тільки lazy()-компоненти,
а й Promise із запиту даних.
2. Що змінилося в React 18
До React 18:
Suspenseвмів чекати тільки ліниво завантажувані компоненти (React.lazy);- для даних доводилося вручну показувати
Loading, перевірятиisLoading, обробляти помилки тощо.
З React 18+:
Suspenseтепер може чекати Promise з будь-якого джерела (fetch, async-функції тощо);- React розуміє, що компонент "ще не готовий", і призупиняє його рендер,
показуючи
fallback; - коли Promise завершено -> React відновлює рендер з реальними даними.
3. Приклад "ручної" реалізації через Promise
Щоб зрозуміти механіку, подивімося на "низькорівневий" приклад без Next.js:
// utils/fetchData.js
export function fetchData(url) {
let status = 'pending';
let result;
const suspender = fetch(url)
.then(res => res.json())
.then(data => {
status = 'success';
result = data;
})
.catch(err => {
status = 'error';
result = err;
});
return {
read() {
if (status === 'pending') throw suspender;
if (status === 'error') throw result;
return result;
},
};
}Тепер використаємо його в компоненті:
const resource = fetchData('/api/user');
function Profile() {
const user = resource.read(); // кидає Promise, якщо дані ще не готові
return <h1>{user.name}</h1>;
}
function App() {
return (
<Suspense fallback={<div>Завантаження профілю...</div>}>
<Profile />
</Suspense>
);
}Що відбувається:
resource.read()кидає Promise -> React "заморожує" рендер компонентаProfile;- React показує
fallbackізSuspense; - Коли Promise завершується -> React "відновлює" рендер і вставляє
Profileз даними.
4. Data Fetching у React Server Components (Next.js 13+)
У React 18+ і Next.js 13/14 Suspense інтегрований глибоко в серверний рендеринг.
Приклад:
// app/page.js (Next.js 13+)
import { Suspense } from 'react';
import UserInfo from './UserInfo';
import UserPosts from './UserPosts';
export default async function Page() {
const user = await getUser(); // async серверний компонент
return (
<>
<h1>{user.name}</h1>
<Suspense fallback={<div>Завантаження постів...</div>}>
{/* UserPosts завантажується незалежно */}
<UserPosts userId={user.id} />
</Suspense>
</>
);
}Тут React:
- віддає першу частину HTML (ім'я користувача) одразу;
- "призупиняє" рендеринг
UserPosts, поки не отримає дані; - пізніше дострімлює HTML для
UserPosts(Streaming SSR); - усе відбувається без перезавантаження сторінки.
Це працює завдяки новим API:
renderToPipeableStream()- Node.js streaming SSR;renderToReadableStream()- для Deno, Edge тощо.
5. Комбінація Suspense + React Query / SWR
Поки React не має "вбудованого" fetch-механізму, але популярні бібліотеки вже додали підтримку Suspense:
import { useQuery } from '@tanstack/react-query';
import { Suspense } from 'react';
function Todos() {
const { data: todos } = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/api/todos').then(res => res.json()),
suspense: true, // ключова опція
});
return <ul>{todos.map(t => <li key={t.id}>{t.title}</li>)}</ul>;
}
export default function App() {
return (
<Suspense fallback={<div>Завантаження задач...</div>}>
<Todos />
</Suspense>
);
}useQuery({ suspense: true }) повідомляє React, що під час завантаження потрібно кидати Promise,
і Suspense автоматично відобразить fallback.
6. Як Suspense працює всередині
- Компонент викликає async-код (або хук), який викидає
Promise; - React "зустрічає" цей
Promise; - React:
- відкладає рендер цієї гілки;
- показує
fallbackз найближчого<Suspense>;
- Коли Promise завершується, React відновлює рендер;
- При SSR React може навіть "дострімити" готовий HTML на клієнт.
7. Що Suspense не робить сам
- Не виконує
fetch()напряму - це робить ваш код чи бібліотека. - Не обробляє помилки (
catch) - для цього потрібен<ErrorBoundary>. - Не замінює
useEffect- Suspense працює під час рендера, а не після.
Підсумок
Suspense в React 18 дозволяє React чекати дані так само, як він чекає компоненти. Він перетворює асинхронність на декларативну конструкцію: React "призупиняє" частину UI і "відновлює" її, коли все готово, без ручного
isLoading,if (!data)тощо.
Коротко:
| Можливість | До React 18 | React 18+ |
|---|---|---|
| Очікування async-компонентів | Ні | Так |
| SSR + Streaming Suspense | Ні | Так |
| Інтеграція з React Query / SWR | Ні | Так |
| Data Fetching в Server Components | Ні | Так |
Fallback-UI без ручного isLoading | Ні | Так |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.