Привет, Хабр! Начиная с версии ReactJS 16.8 в наш обиход вошли хуки.  Этот функционал вызвал много споров, и на это есть свои причины. В данной статье мы рассмотрим одно из самых популярных заблуждений использования хуков и заодно разберемся стоит ли писать компоненты на классах (данная статья является расшифровкой видео). Как вы знаете в реакте есть 2 вариант написания компонента, с помощью классов и с помощью функций. И каждый вариант по своему взаимодействует с методами. Давайте рассмотрим оба варианта: Первый вариант, это использовать классы: В данном варианте мы добавили метод onClick классу Test и при создании инстанса класса, этот метод создается 1 раз и в рендере мы уже используем ссылку на этот метод onClick={this.onClick}, таким образом при каждом рендере мы обращаемся всегда к одной и той же ссылке и не пересоздаем метод класса. Эта конструкция всем, кто давно в профессии, привычна и понятна даже если вы недавно пришли в React с другого языка программирования. Второй способ создания компонента является использование функции: В таком подходе, чтобы создать обработчик onClick, мы описываем тело функции прямо внутри render, потому что все тело функции и есть render, другого варианта в принципе не существует, если вы хотите использовать props. И тут у нас начинает зудеть в боку, да как это так, мы же теперь заново создаем функцию, абсолютно на каждый рендер. По сравнению с классами это как будто шаг назад. Чтобы разобраться с этим вопросом я полез в React документацию в секцию вопросы и ответы и нашел там следующий вопрос: Судя по документации создание инстанса класса для реакта настолько дорогостоящая операция, что создавать функцию на каждый рендер на порядок дешевле. Да и тот факт, что дерево становится глубже при использовании компонента высшего порядка connect от redux или бесконечных observer от mobX совсем не радует. Идею, создавать на каждый рендер новую функцию и думать что это дешевле, чем один раз создать инстанс класса, немного сложно принять разработчикам, потому что с нашей стороны мы должны писать код хуже, и верить что приложение ускорится. Звучит крайне противоречиво, а  мы привыкли все оптимизировать. Как результат, мы начинаем искать пути, как выйти на прежний уровень оптимизации с нашей стороны. И первое что гуглится, это начать использовать хук useCallback. И многие особо не вникая в суть происходящего начинают его активно использовать. Чтобы разобраться во всем этом давайте устроим небольшую викторину Сейчас мы рассмотрим 2 примера и Вы попытаетесь ответить кто круче! В одном углу ринга находится уже изученный нами ранее вариант написания обработчика события someFunction: В другом углу ринга находится точно такой же компонент, но уже решили завернуть функцию в useCallback. Для пользователя ничего не изменилось, console.log(title), точно так же вызывается при нажатии на кнопку. В каком из вариантов написания компонента функция присваемая переменной someFunction создается реже?   Даем минутку подумать...         Аккуратно ответ!     И правильный ответ ни в каком! Да именно, никакой оптимизации useCallback нам не дал, функция создается ровно столько же раз, как и до оптимизации. Более того, мы наоборот ухудшили перфоманс нашего компонента. То что многих вводит в заблуждение - это представление что useCallback, это какой-то черный ящик, в который ты отдаешь функцию, с ней что-то происходит и после будет тебе счастье. Но давайте рассмотри как это работает на самом деле. Для начала сделаем typeof этого черного ящика: Естественно мы получим результат function. По синтаксису это было очевидно. Чтобы понять как работает черный ящик, давайте сами напишем имплементацию useCallback. useCallback - это функция, которая принимает 2 параметра, callback и deps. Далее нам надо хранить где-то этот callback и deps, чтобы иметь возможность при очередном вызове вернуть ту же самую функцию callback. Теперь рассмотрим разные случаи: если deps не существует либо в prevState, либо в новых данных, тогда нужно сохранить текущие параметры и вернуть callback. Если же deps существуют. Тогда сравниваем какой-либо функцией массивы и если они совпадают, тогда возвращаем мемоизированную функцию. Ну и если deps не совпадают, тогда снова сохраняем параметры и возвращаем текущий callback. Вроде бы мы покрыли все кейсы Функция useCallback как и любая другая функция вызывается на каждый рендер и в качестве параметра callback каждый рендер приходит новая функция и новый массив зависимостей. Которые мы либо выбрасываем, если зависимости до и после совпадают, либо сохраняем в хранилище, для будущего использования. Давайте теперь посмотрим на эту функцию со стороны компонента. Мы знаем, что useCallback это просто функция и мы можем извлечь передаваемые параметры в отдельные переменные. Тут становится совсем очевидно, что мы на каждый рендер создаем не то что функцию, а еще и массив с зависимостями, а потом еще и прокручиваем все это через useCallback. Если мы просто закомментируем создание зависимостей и вызов useCallback и передадим параметр callback напрямую в onClick, тогда кажется перфоманс компонента должен улучшиться, ведь мы убрали посредника, который не нес никакой пользы для перфоманса. По итогу мы вернулись к начальной ситуации. Когда просто создавали функцию на каждый рендер.  Получается, в данном случае использовать хук useCallback - это не значит улучшить перфоманс, а скорее  совсем наоборот, ухудшить перфоманс.  Получается мы как то не так используем useCallback. Чтобы разобраться в этом, давайте обратимся к документации: Получается основная идея не в улучшении перформанса в конкретном компоненте, а скорее использование useCallback выгодно только в случае передачи функции как props. Давайте рассмотрим еще один пример. Допустим у нас есть список машин, который мы хотим отобразить: Тут нам понадобилось добавить обработчик клика на машину. Мы создаем метод onCarClick и передаем его в компонент Car. В такой ситуации на каждый рендер компонента Cars у нас создается новая функция onCarClick, соответственно, не важно Car  это PureComponent или обернут в memo, все машины всегда будут заново рендерится, т.к. получают каждый раз новую ссылку на функцию. Для этого и нужен useCallback, если мы завернем функцию в хук, то у нас в переменной onCarClick будет уже возвращаться мемоизированая функция, хоть мы в нее на каждый рендер и передаем новую функцию Таким образом все компоненты Car не будут рендериться лишний раз, т.к. ссылка на функцию останется прежней.  А если заглянуть внутрь компонента Car. Там мы создадим еще одну функцию, которая свяжет onCarClick и объект car. В этом случае нет никакой пользы оборачивать метод в useCallback, т.к. нам не важно, ссылка это на функцию с прошлого рендера или с текущего рендера, а useCallback как мы уже знаем не бесплатный. Подытожить данную статью можно следующими словами. React хоть и поддерживает компоненты в виде классов, но имеет больше маневров над ускорением именно компонентов в виде функций. Да и сама экосистема все больше в качестве API предоставляет вам именно хуки, что невозможно использовать в классах:  И конечно, доверяйте реакту, если они сказали лучше создавать функцию на каждый рендер, так и делайте, ведь они заинтересованы только в улучшении перформанса вашего проекта. А если вам понравилось данная статья, то здесь есть еще немного интересного. Чао User