Comments 8
Спасибо за статью.
А сможете добавить пару слов о том как пофиксить случай из самого первого примера?
Который перед словами
Помимо создания функции при каждом вызове компонента теперь дополнительно создаётся массив зависимостей...
Для данного примера
const handleClick = () => console.log('Clicked');
const Component = () => {
return <div onClick={ handleClick }>...</div>;
);
Но этот пример не совсем из реальной жизни. Под оптимизацией React компонентов чаще всего понимают оптимизацию лишних ререндеров, а не оптимизацию по памяти и побочных расчетов, которые не дадут заметное ускорение производительности (сотни наносекунд можно даже не увидеть в профайлере). И в общем случае useCallback спасет от перендера функционального компонента.
Лучше уделить внимание на использование этого компонента, тк можно написать такой код, что любой мемоизированный компонент будет создаваться заново при каждом чихе.
Здравствуйте! Рада, если статья оказалась Вам полезна.
Как Вам ответили ранее - не стоит использовать useCallback для данного кейса: он полезен, если мы хотим получать одну и ту же функцию, чтобы избежать лишних рендеров именно дочерних компонентов или, например, если эта функция является зависимостью других хуков.
Можно вынести функцию за пределы компонента, если она не зависит от props и state, либо оставить её, как есть. Про влияние создания функций при каждом рендере на производительность можно прочитать ответ в официальной документации React: https://ru.reactjs.org/docs/hooks-faq.html#are-hooks-slow-because-of-creating-functions-in-render
Спасибо за примерчики.
IMHO, пора обобщить опыт и написать грамотное руководство по "кошерным" паттернам React с подробными примерами.
Сеттеры стейта - уже мемоизованные функции (согласно документации), поэтому их не нужно обарачивать в useCallback.
Добрый день! Спасибо за комментарий) Да, так и есть: Реакт гарантирует, что она не изменится.
В данном случае стоит заметить, что в примерах в useCallback передается не сама функция-сеттер, а еще одна стрелочная функция, которая содержит этот сеттер. Это пример, в реальности эта функция могла вызывать не только сеттер, но и отправлять метрику на click, например, или вызывать еще один колбэк.
Есть хорошее правило: мемоизированные пропсы (функция из useCallback, объект из useMemo и т.д.) надо передавать только в классовый компонент на основе PureComponent, в классовый компонент с методом shouldComponentUpdate, или в функциональный компонент, обёрнутый в memo (в крайнем случае допустимо использовать несколько промежуточных компонентов-обёрток, пробрасывающих эти пропсы дальше). Иначе эта мемоизация - только пустая трата CPU и памяти. То есть ещё в момент написания кода вы должны осознавать, что передавать useCallback в простой div, как в первом примере - явно избыточно.
Ещё один приём, применимый не только для React, но и для JavaScript в целом: можно сэкономить время, поднимая медленные повторяющиеся вычисления до максимально возможной области видимости (что-то вроде ручного invariant code motion). В данном примере `const onClick = () => console.log('Clicked');` никак не зависит от пропсов компонента, а значит может быть поднято на уровень выше. Хотя здесь мы подходим опасно близко к "не занимайтесь преждевременной оптимизацией": создание функций в JS в целом и в V8 в частности сделано максимально дешёвым, профилирование компонентов мы не проводили, реально ли упираемся по производительности именно в создание функции именно в этом месте - мы не знаем. Поэтому этот пример можно оставить как есть (без useCallback), но запомнить, что как только профилирование покажет проблемы в этом месте - есть возможность немного сэкономить на создании функции onClick.
И раз уж коснулись именно обработки событий - не забывайте про event delegation. Если у вас десятки и сотни однотипных компонентов в каком-то контейнере, и/или есть часто перерендеривающиеся компоненты - можно создать и навесить один обработчик onClick на контейнер, а не N обработчиков на каждый компонент в контейнере. При этом код обработчика усложняется (надо разбираться, с какого именно компонента всплыл клик и как его дальше обрабатывать), но снижаются затраты на создание и навешивание обработчиков событий.
Оптимизация рендеринга React-компонентов: как не навредить