Запити в тілі компонента
Чому не можна робити запити в тілі компонента
1. Компонент може рендеритися багато разів
React викликає компонент як звичайну функцію при кожному рендері:
function User() {
console.log('render');
// ...
return <div>...</div>;
}Якщо всередині тіла компонента ви напишете запит:
function User() {
fetch('/api/user') // невірно
return <div>...</div>;
}цей fetch() буде виконуватися при кожному рендері, а рендерів може бути десятки:
- через зміну
state; - через зміну
props; - через контекст;
- через батьківський рендер;
- у React 18 - навіть двічі в dev-режимі (Strict Mode).
У результаті ви отримаєте безліч однакових запитів, навантаження на мережу і "мерехтливий" інтерфейс.
2. React вимагає чистих компонентів
Компонент у React має бути чистою функцією:
та сама функція з однаковими вхідними даними (props/state) завжди повинна повертати однаковий JSX без побічних ефектів.
Асинхронний запит (fetch, axios, localStorage, WebSocket) - це побічний ефект:
він змінює зовнішній світ, робить HTTP-виклик і не є детермінованим.
React очікує, що:
render(props) → JSXа не:
render(props) → побічний ефект + JSXТому такі операції потрібно виносити в ефекти (useEffect), які React виконує після рендера,
коли безпечно виконувати асинхронні дії.
3. Асинхронні операції ламають фазу рендера
Якщо зробити await прямо в тілі:
async function User() {
const res = await fetch('/api/user'); // невірно
const user = await res.json();
return <p>{user.name}</p>;
}React не буде чекати цей await - він просто побачить, що компонент повернув Promise замість JSX, і видасть помилку.
Виняток - React Server Components (RSC) у Next.js 13+:
там серверні компоненти дійсно можуть бути async,
тому що рендер виконується на сервері, де React вміє "дочекатися" await.
Але в клієнтських компонентах (звичайні React-застосунки) це неможливо.
4. Ризик нескінченного циклу
Якщо ви записуєте результат запиту в стан прямо в тілі:
function User() {
const [data, setData] = useState();
fetch('/api/user')
.then(res => res.json())
.then(setData);
return <p>{data?.name}</p>;
}то при кожному setData() -> викликається новий рендер -> знову fetch() -> знову setData()...
Нескінченний цикл запитів і завислий інтерфейс.
Як правильно
Використовувати useEffect():
function User() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(setData)
.catch(console.error);
}, []); // порожній масив залежностей = виконається один раз при монтуванні
if (!data) return <p>Loading...</p>;
return <p>{data.name}</p>;
}Підсумок
| Робити запит у тілі компонента | Робити запит у useEffect |
|---|---|
| Виконується при кожному рендері | Виконується після монтування |
| Створює побічний ефект під час рендера | Побічний ефект виконується контрольовано |
| Може призвести до нескінченних запитів | Виконується один раз або за потрібними залежностями |
| Порушує принцип "чистої функції" | Дотримується React-парадигми "рендер -> ефект" |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.