Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Suspense і Data fetching». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`Suspense`** дозволяє React призупиняти рендеринг частини дерева, поки компонент не отримає потрібні дані, і показувати fallback-UI, причому тепер він може чекати не тільки `lazy()`-компоненти, а й Promise із запиту даних. **Ключове:** у React 18+ компонент чи хук кидає Promise, React "заморожує" рендер цієї гілки, показує fallback з найближчого `<Suspense>`, а коли Promise завершується, відновлює рендер з реальними даними.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## 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: ```javascript // 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; }, }; } ``` Тепер використаємо його в компоненті: ```javascript 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> ); } ``` Що відбувається: 1. `resource.read()` кидає Promise -> React "заморожує" рендер компонента `Profile`; 2. React показує `fallback` із `Suspense`; 3. Коли Promise завершується -> React "відновлює" рендер і вставляє `Profile` з даними. --- ## 4. Data Fetching у React Server Components (Next.js 13+) У **React 18+** і **Next.js 13/14** Suspense інтегрований глибоко в серверний рендеринг. ### Приклад: ```javascript // 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: ```javascript 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 працює всередині 1. Компонент викликає async-код (або хук), який викидає `Promise`; 2. React "зустрічає" цей `Promise`; 3. React: - відкладає рендер цієї гілки; - показує `fallback` з найближчого `<Suspense>`; 4. Коли Promise завершується, React **відновлює** рендер; 5. При 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` | Ні | Так |Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.