Оптимизация через мемоизацию, управление побочными эффектами и обработка событий

Эффект не должен быть async; resize-слушатель нужно удалить и вызвать сразу для начальной ширины. Callback Counter с [] хранит устаревший onClick и ломает дальнейшее увеличение счётчика.

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

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

Эффект не должен быть async; resize-слушатель нужно удалить и вызвать сразу для начальной ширины. Callback Counter с [] хранит устаревший onClick и ломает дальнейшее увеличение счётчика.

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

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

Условие

Выполните ревью кода приведённого ниже React‑приложения:

const App = () => {
  const [count, setCount] = useState(0);
  const [date, setDate] = useState<string>();
  const [clientWidth, setClientWidth] = useState<number>();

  useEffect(async () => {
    setDate(await fetchDate());

    window.addEventListener("resize", () => {
      setClientWidth(document.body.clientWidth);
    });
  }, []);

  return (
    <div className="App">
      <div key="title">Server date: {date}</div>
      <div key="width">Client width: {clientWidth}px</div>
      <Counter
        value={count}
        onClick={() => setCount(count + 1)}
      />
    </div>
  );
};

function Counter(props: any) {
  console.log("COUNTER rendered");

  const memoizedOnClick = useCallback(() => {
    props.onClick();
  }, []);

  return (
    <div>
      <button onClick={memoizedOnClick}>+&nbsp;</button>
      {props.value || 0}
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Ответ

Эффект не должен быть async; resize-слушатель нужно удалить и вызвать сразу для начальной ширины. Callback Counter с [] хранит устаревший onClick и ломает дальнейшее увеличение счётчика.

В App используйте стабильный const increment = useCallback(() => setCount(value => value + 1), []) и передавайте его в Counter. В Counter достаточно onClick={onClick}; если мемоизацию требуется оставить, её зависимость — [props.onClick], не []. Эффект оформите обычной функцией с внутренним async-вызовом, флагом active для позднего ответа и catch для ошибки. Сохраните одну функцию resize, измеряющую window.innerWidth, вызовите её при подписке и удалите при cleanup. Тип props — { value: number; onClick: () => void }; any и лишние key не нужны. Формат fetchDate следует проверить по реальному API. Для современного React точка входа использует createRoot(rootElement).render(<App />) из react-dom/client после проверки rootElement.

Источник: react.dev.

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

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

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

Подробнее