Оптимизация и рефакторинг React‑компонента

Компонент накапливает обработчики и интервалы, а таймеры захватывают старый count. Нужны очистка эффекта, актуальное значение через ref и согласованное обновление связанных count/items.

Короткий ответ

Что ответить на собеседовании

Компонент накапливает обработчики и интервалы, а таймеры захватывают старый count. Нужны очистка эффекта, актуальное значение через ref и согласованное обновление связанных count/items.

Подробный разбор

Ответ с пояснениями

Условие

Необходимо выполнить рефакторинг представленного React‑компонента, устранив потенциальные проблемы и улучшив его структуру и читаемость.

import React from 'react';

const PleaseReviewMe = () => {
  const [count, setCount] = React.useState(1);
  const [items, setItems] = React.useState([{ id: 1 }]);

  React.useLayoutEffect(() => {
    document.addEventListener('click', () => {
      setInterval(() => console.log(count), 1000);
    });
  });

  const click = React.useCallback(() => {
    setCount(count + 1);
    setItems([...items, { id: count + 1 }]);
  });

  return (
    <React.Fragment>
      Current count: {count}
      <ul>
        {items.map((item) => (
          <li key={item.id}>{item.id}</li>
        ))}
      </ul>
      <button onClick={() => click()}>add once</button>
    </React.Fragment>
  );
};

export default PleaseReviewMe;

Ответ

Компонент накапливает обработчики и интервалы, а таймеры захватывают старый count. Нужны очистка эффекта, актуальное значение через ref и согласованное обновление связанных count/items.

useLayoutEffect без зависимостей запускается после каждого commit; анонимный обработчик нельзя снять без сохранения ссылки. Каждый клик дополнительно создаёт вечный интервал. Если нужен один перезапускаемый логгер, используйте такую часть компонента:

const latest = React.useRef(count);
React.useEffect(() => { latest.current = count; }, [count]);
React.useEffect(() => {
  let timer;
  const onClick = () => {
    clearInterval(timer);
    timer = setInterval(() => console.log(latest.current), 1000);
  };
  document.addEventListener('click', onClick);
  return () => {
    document.removeEventListener('click', onClick);
    clearInterval(timer);
  };
}, []);

Для добавления элемента объедините count/items в один state или reducer и вычисляйте следующий объект из предыдущего. useCallback без массива зависимостей не сохраняет функцию между рендерами; здесь его можно убрать. Измерений layout нет, поэтому синхронный layout-эффект не нужен. Перезапуск единственного таймера — явно выбранная семантика, а не сохранение накопления интервалов.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее