Интервью-вопрос
Что относится к побочным эффектам
Побочный эффект это действие, которое выходит за рамки чистого вычисления и работает с внешним миром. На практике важно не только назвать примеры, но и показать, как контролировать lifecycle эффекта, чтобы не получить лишние запросы, утечки и stale data.
- Добавлен
- Редакция
Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.
Мини-квиз
Проверка перед разбором
Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.
Вопрос 1 из 60 правильно
Разбор
Разобраться, а не зазубрить
Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.
Базовая идея
Побочный эффект начинается там, где код перестает быть обычным вычислением значения. Чистая функция получает аргументы и возвращает результат. Если вызвать ее с теми же аргументами, она даст тот же результат и не изменит ничего вокруг.
Побочный эффект связан с внешним миром. Например, отправить запрос, записать в localStorage, изменить DOM, подписаться на событие, запустить таймер, отправить событие аналитики, изменить внешнюю переменную или состояние другого модуля.
На интервью вам удобно идти в три шага. Сначала дайте критерий, потом приведите примеры, потом назовите практический риск. Такой код сложнее повторять, тестировать и безопасно отменять. Если эффект запустится не тогда, когда вы ожидали, UI может показать старые данные, создать лишний запрос или оставить незакрытую подписку.
Как отличить эффект от вычисления
Простая проверка такая. Можно ли убрать внешний мир и оставить только входные данные. Если да, перед вами обычное вычисление. Если коду нужен браузерный API, сеть, время, глобальное состояние или он что-то меняет снаружи, это уже эффект или как минимум нечистая логика.
Важно не смешивать похожие вещи. Date.now() и Math.random() не обязательно меняют внешний мир, но делают функцию недетерминированной. Запись в localStorage, вызов fetch, addEventListener или setInterval уже точно требуют контроля жизненного цикла.
Куда поместить такой код
Оставьте ее в render. Если вычисление дорогое, подумайте про useMemo.Обычно это обработчик события, а не эффект после каждого render.Это побочный эффект. Используйте useEffect или отдельный data layer. Для storage и window проверьте SSR-среду.Это редкий случай для useLayoutEffect. Сначала проверьте, что без него действительно есть визуальный скачок.Где побочным эффектам место в React
В React тело компонента должно быть чистым описанием UI для текущих props и state. Поэтому во время render не стоит делать запросы, подписки, запись в storage и ручные изменения DOM. React может вызвать render повторно, пропустить результат или размонтировать и смонтировать компонент снова.
Плохой пример: запрос запускается во время render.
function UserCard({ userId }: { userId: string }) {
// Плохо: это побочный эффект во время render.
fetch(`/api/users/${userId}`);
return <div>Loading...</div>;
}Что сломается. Каждый повторный render может создать новый запрос. Старые ответы могут перезаписать свежие данные. В development вы увидите дубли. Безопаснее держать такой код там, где есть понятный lifecycle. Для простого client-side запроса это может быть useEffect. В приложениях с роутерами и фреймворками часто лучше использовать loader, server component, query library или другой слой данных.
Пример с контролем запроса, loading state и защитой от устаревшего результата:
function UserCard({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
async function loadUser() {
try {
setIsLoading(true);
setError(null);
setUser(null);
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
});
if (!response.ok) {
throw new Error("Request failed");
}
const nextUser = await response.json();
setUser(nextUser);
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") {
return;
}
setError("Не удалось загрузить пользователя");
} finally {
if (!controller.signal.aborted) {
setIsLoading(false);
}
}
}
loadUser();
return () => {
controller.abort();
};
}, [userId]);
if (isLoading) return <p aria-live="polite">Loading...</p>;
if (error) return <p role="alert">{error}</p>;
if (!user) return <p>Пользователь не найден</p>;
return <div>{user.name}</div>;
}Здесь эффект зависит от userId. Старый запрос отменяется при смене id или размонтировании. Ошибка отмены не показывается пользователю как реальная ошибка. UI не оставляет на экране старого пользователя во время новой загрузки и отдельно показывает ошибку или пустое состояние. Это не единственный способ. Главное, что эффект имеет setup, зависимости и cleanup.
Что стоит назвать в примерах
Самые понятные примеры для frontend-собеседования:
- HTTP-запросы через
fetchили клиент API. - Подписки на события:
addEventListener, media query listener, WebSocket. - Таймеры:
setTimeout,setInterval, animation loop. - Работа с браузерным хранилищем:
localStorage,sessionStorage, cookies, IndexedDB. - Ручное чтение или изменение DOM, измерение layout, focus management.
- Отправка аналитики, логирование, запись во внешние сервисы.
- Мутация внешних объектов, кэшей и глобальных переменных.
Не нужно перечислять все подряд. Выберите 4-5 примеров и для одного сразу назовите риск. Например, если подписаться на resize и не снять обработчик, компонент уже исчезнет, а обработчик продолжит работать.
Жизненный цикл эффекта
Эффект лучше объяснять не как кусок кода, который просто запускается после render, а как синхронизацию с внешней системой. У синхронизации есть начало, обновление и завершение.
Для подписки начало это addEventListener, завершение это removeEventListener. Для таймера начало это setInterval, завершение это clearInterval. Для запроса начало это вызов API, а завершение может быть отменой через AbortController или проверкой, что ответ еще актуален.
- 1Создать ресурс внутри эффекта
- 2Указать все зависимости, которые используются внутри
- 3Вернуть cleanup для подписок, таймеров и listeners
- 4Отменить запрос или проигнорировать устаревший ответ
- 5Проверить поведение при повторном mount в Strict Mode
- 1Запустить запрос или таймер прямо во время render
- 2Спрятать зависимость, чтобы эффект не перезапускался
- 3Не удалить listener или interval при размонтировании
- 4Обновить state из старого ответа после нового запроса
- 5Рассчитывать, что эффект всегда сработает один раз
Что сказать про Strict Mode
Если вас спрашивают про повторный запуск эффектов, не называйте это багом React. В development Strict Mode помогает найти эффекты, которые не готовы к повторному mount или cleanup. Поэтому эффект не должен полагаться на то, что он выполнится ровно один раз за всю жизнь приложения.
Практический вывод такой. Не используйте глобальные флаги как основной способ запретить второй запуск. Лучше сделать effect setup и cleanup корректными. Если аналитика или платежный запрос действительно не должны дублироваться, это нужно решать на уровне события, идемпотентности API, ключа операции или серверной защиты, а не надеждой на один render.
Практический вывод
Сильный ответ на этот вопрос звучит не как список терминов, а как правило для безопасного UI. Побочные эффекты неизбежны, потому что frontend общается с сетью, браузером и пользователем. Проблема не в самом эффекте, а в том, где он расположен и кто контролирует его жизненный цикл.
На интервью можно завершить так:
Я стараюсь держать вычисления и render чистыми, а эффекты изолировать. Для каждого эффекта проверяю, что его запускает, от каких данных он зависит, что произойдет при повторном запуске и как он очищается. Это снижает риск утечек, лишних запросов, stale data и странного поведения в React.
Частые ошибки
Где обычно ошибаются
Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.
- 1
Называть эффектом любой асинхронный код
Асинхронность сама по себе не равна побочному эффекту. Например, вычисление значения в
Promiseможет быть чистым, если оно не трогает внешний мир. На интервью лучше звучит ответ с критерием. Код меняет внешнее состояние или зависит от внешней системы. - 2
Делать эффекты во время render
Render в React должен быть чистым. Если вызвать
fetch, записать вlocalStorageили подписаться на событие в теле компонента, React может повторить это действие при каждом render. В итоге вы получите лишние запросы, дубли аналитики, утечки и гонки состояния. - 3
Забывать cleanup
Таймеры, обработчики событий, observers и подписки продолжают работать после размонтирования, если их не остановить. Это дает утечки памяти и обновления state в компоненте, которого уже нет. Безопасная формулировка простая. Эффект создает ресурс, cleanup освобождает его.
- 4
Прятать зависимости эффекта
Пустой массив зависимостей не лечит лишние перезапуски, если эффект использует меняющиеся props или state. Он может зафиксировать старое значение и привести к stale closure. Лучше честно указать зависимости или изменить архитектуру. Например, перенести действие в handler.
- 5
Использовать useEffect для derived state
Если значение можно вычислить из текущих props и state, эффект часто не нужен. Сохранение такого значения в отдельный state добавляет лишний render и риск рассинхронизации. Обычно достаточно вычисления в render. Для тяжелой операции можно рассмотреть
useMemo. - 6
Игнорировать race condition в запросах
Если пользователь быстро меняет фильтр или id, старый ответ может прийти позже нового и перезаписать актуальные данные. Для API-запросов стоит использовать
AbortControllerили проверку актуальности ответа. Иначе UI покажет неверное состояние. - 7
Забывать про loading, error и empty state
Эффект может быть технически правильным, но UI все равно будет плохим. Например, при смене
userIdна экране останется старый пользователь, ошибка отмененного запроса попадет в alert, а пустой список будет выглядеть как вечная загрузка. Для сетевых эффектов стоит явно продуматьloading,errorи пустой результат. - 8
Читать browser-only API во время SSR
window,documentиlocalStorageдоступны только в браузере. Если прочитать их в render на сервере, страница может упасть или гидрироваться с другим HTML. Безопаснее читать такие данные в эффекте, проверять окружение или передавать начальные значения из серверного слоя. - 9
Вставлять внешние данные в DOM вручную
Ручное изменение DOM тоже побочный эффект. Если взять HTML из API и вставить его через
innerHTMLили похожий API без санитизации, можно получить XSS. Безопаснее рендерить строки как текст средствами React. Для доверенного HTML используйте проверенную санитизацию и понятную политику источников.
Follow-up
Что могут спросить дальше
Короткие ответы на вопросы, которыми проверяют понимание побочных эффектов, React lifecycle и безопасной работы с API.
Живые ответы
Видео с похожим вопросом
Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.
Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.
Что делать, если при запросе приходит 500 😎
500 означает внутреннюю ошибку сервера, но фронтенд все равно должен проверить корректность запроса, собрать контекст и показать пользователю безопасное состояние. На странице разбираем, что сказать на интервью и как не ухудшить UX повторными запросами.
Будет ли работать CORS, если отправить запрос через Postman 😎
CORS проверяет браузер, а не Postman. Разбираем, почему запрос из Postman может пройти, хотя браузерный fetch падает с CORS-ошибкой, и что это значит для защиты API.