Эффект не должен быть 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}>+ </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.