Будет ли срабатывать UseEffect при перерисовке родительского компонента?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Срабатывает ли useEffect при перерисовке родительского компонента
Ответ зависит от зависимостей useEffect. Если dependency array содержит значение, которое изменилось, то useEffect срабатывает. Если родитель перерисовался, но зависимости не изменились, useEffect НЕ срабатывает.
Как работает useEffect
import { useEffect, useState } from 'react';
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
<Child /> {/* Перерисовывается каждый раз */}
</div>
);
}
function Child() {
// Срабатывает при: компонент монтируется, зависимости меняются
useEffect(() => {
console.log('Child useEffect fired');
}, []); // Зависимостей нет - срабатывает только при монтировании
return <p>Child Component</p>;
}
В этом примере Parent перерисовывается каждый клик на кнопку, но Child.useEffect срабатывает только один раз при монтировании.
Зависимости useEffect
1. Без dependency array — срабатывает после КАЖДОЙ перерисовки
function Child() {
const [data, setData] = useState(null);
useEffect(() => {
console.log('useEffect without dependencies');
// Срабатывает после КАЖДОЙ перерисовки
}); // Нет массива зависимостей!
return <p>Child: {data}</p>;
}
// Родитель кликает на кнопку 5 раз
// Консоль: useEffect fired 5 раз (опасно! может привести к infinite loops)
2. Пустой dependency array — срабатывает один раз при монтировании
function Child() {
useEffect(() => {
console.log('useEffect with empty dependencies');
// Срабатывает ДО ОДНОГО раза при монтировании
}, []); // Пустой массив - монтирование/размонтирование
return <p>Child</p>;
}
// Родитель перерисовывается много раз
// Консоль: useEffect fired 1 раз (правильно!)
3. С зависимостями — срабатывает когда зависимости меняются
function Child({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
console.log(`Fetching user ${userId}`);
fetchUser(userId).then(setUser);
}, [userId]); // Только когда userId меняется
return <p>User: {user?.name}</p>;
}
// Родитель:
// userId: 1 -> useEffect fires (fetches user 1)
// userId: 1 -> useEffect НЕ fires (userId не изменился)
// userId: 2 -> useEffect fires (fetches user 2)
// userId: 2 -> useEffect НЕ fires (userId не изменился)
Практические примеры
Пример 1: Fetch данных при монтировании
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Срабатывает только при монтировании или изменении userId
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, [userId]); // Важно указать все внешние зависимости!
if (loading) return <p>Loading...</p>;
return <p>{user.name}</p>;
}
// Использование:
// <UserProfile userId={1} /> -> useEffect fires
// Родитель перерисовывается -> useEffect НЕ fires (userId тот же)
// <UserProfile userId={2} /> -> useEffect fires (userId changed)
Пример 2: Подписка на события (очистка в cleanup)
function WindowResizeListener() {
useEffect(() => {
const handleResize = () => {
console.log(`Window resized: ${window.innerWidth}x${window.innerHeight}`);
};
window.addEventListener('resize', handleResize);
console.log('Listener added');
// Cleanup функция: срабатывает перед размонтированием
// или перед следующим useEffect
return () => {
window.removeEventListener('resize', handleResize);
console.log('Listener removed');
};
}, []); // Монтирование/размонтирование
return <p>Resize the window...</p>;
}
// Родитель перерисовывается много раз
// Консоль: Listener added 1 раз (правильно, не создаем новый listener каждый раз)
Пример 3: Опасность без зависимостей
function DangerousComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('useEffect fired');
// Это срабатывает после КАЖДОЙ перерисовки!
setCount(count + 1); // Меняет state -> перерисовка -> useEffect -> и снова...
}); // БЕЗ зависимостей = срабатывает всегда
return <p>Count: {count}</p>;
}
// Результат: INFINITE LOOP
// useEffect fired
// useEffect fired
// useEffect fired
// ... до тех пор, пока приложение не зависнет
Пример 4: Правильное использование множественных зависимостей
function SearchResults({ query, category, sortBy }) {
const [results, setResults] = useState([]);
useEffect(() => {
// Срабатывает когда query, category ИЛИ sortBy меняются
console.log(`Fetching: query=${query}, category=${category}, sortBy=${sortBy}`);
fetch(`/api/search?q=${query}&cat=${category}&sort=${sortBy}`)
.then(res => res.json())
.then(setResults);
}, [query, category, sortBy]); // ВСЕ зависимости должны быть указаны!
return (
<div>
{results.map(r => <div key={r.id}>{r.title}</div>)}
</div>
);
}
// Родитель:
// query меняется -> useEffect fires
// category меняется -> useEffect fires
// sortBy меняется -> useEffect fires
// Родитель перерисовывается без изменения этих props -> useEffect НЕ fires
Жизненный цикл useEffect
function EffectLifecycle({ id }) {
useEffect(() => {
console.log(`1. Effect running (id=${id})`);
return () => {
console.log(`2. Cleanup (id=${id})`);
// cleanup срабатывает перед:
// - размонтированием компонента
// - перед следующим useEffect (если зависимости изменились)
};
}, [id]);
return <p>ID: {id}</p>;
}
// Сценарий 1: Монтирование
// -> Effect running (id=1)
// Сценарий 2: id меняется 1 -> 2
// -> Cleanup (id=1)
// -> Effect running (id=2)
// Сценарий 3: Размонтирование
// -> Cleanup (id=2)
Оптимизация: предотвращение ненужных effect'ов
Проблема: Effect запускается слишком часто
// ПЛОХО: props передаются в useEffect каждый раз
function Child({ user }) {
useEffect(() => {
console.log('Fetching posts for', user.id);
}, [user]); // user это новый объект каждый раз!
}
function Parent() {
const [count, setCount] = useState(0);
const user = { id: 1, name: 'John' }; // Новый объект каждый раз!
return (
<>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child user={user} /> {/* Effect срабатывает каждый клик! */}
</>
);
}
Решение 1: Зависимость на нужное значение
function Child({ user }) {
useEffect(() => {
console.log('Fetching posts for', user.id);
}, [user.id]); // Зависимость на строку, не на объект
}
function Parent() {
const [count, setCount] = useState(0);
const userId = 1;
return (
<>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child userId={userId} />
</>
);
}
Решение 2: Мемоизация с useMemo
function Child({ user }) {
useEffect(() => {
console.log('Fetching posts for', user.id);
}, [user.id]);
}
function Parent() {
const [count, setCount] = useState(0);
const user = useMemo(() => ({ id: 1, name: 'John' }), []); // Не меняется
return (
<>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child user={user} /> {/* user всегда одинаковый */}
</>
);
}
Вывод
useEffect НЕ срабатывает просто так при перерисовке родителя.
Он срабатывает когда:
- Компонент монтируется (если есть dependency array)
- Зависимости меняются (если указаны в array)
- Нет dependency array (срабатывает после КАЖДОЙ перерисовки - опасно!)
Правильное использование:
// Один раз при монтировании
useEffect(() => { }, []);
// Когда specific dependencies меняются
useEffect(() => { }, [dependency1, dependency2]);
// После КАЖДОЙ перерисовки (избегай!)
useEffect(() => { });
Главное правило: Всегда указывайте dependency array. Без него можно создать infinite loops и убить производительность.