Pull to refresh

Comments 26

Иногда useCallback и useMemo используется не для оптимизации как таковой, а чтобы избежать повторных вызовов с useEffect. Например если вы передаете handler в дочерний компонент, а в дочернем этот handler используется в useEffect. Без useCallback ваш handler каждый раз будет новым, что приведет к повторным вызовам useEffect.

Иногда в командах есть договоренность просто все функции которые уходят в дочерние компоненты оборачивать в useCallback, как раз чтобы потом не ловить баги с useEffect. В небольших проектах это не сильно снижает производительность, зато гарантирует что в компонент вам приходит что-то, что не будет меняться при каждой перерисовке родителя.

Например если вы передаете handler в дочерний компонент

Именно этот случай я и описал в начале статьи, как верный способ использования useCallback

Пытаться доказывать что-то собеседующей стороне изначально бесполезная трата усилий. Для них же важна как правило не техническая сторона а социальная - вот этот "хрен с горы" будет втаптывать мой авторитет в грязь (и тем хуже, если обоснованно).

но функции же создаются заново!!

Функции создаются заново в любом случае. useCallback отбрасывает новый экземпляр и возвращает предыдущий, если зависимости не изменились. Это почи, что базовый JS.

По тексту похоже, что они с собеседующим просто не поняли друг друга.

По тексту очевидно, что автор статьи действительно не понимает этого, а значит и самых базовых принципов работы хуков. Собеседующий так и не смог добиться от автора статьи правильного объяснения почему там useCallback не нужен, даже "забайтив" фразой/вопросом "ну функции же создаются заново".

PS. Количество лайков статьи многое говорит об аудитории хабра.

Создаются, но не с нуля же? Не смотрел, но полагаю, что под капотом там создаётся только объект/структура из 2 полей «ссылка на код, скомпилированный компилятором при первом проходе» и «ссылка на замыкание», что по затратам где-то на уровне нуля.

Не только функции, но и массив зависимостей создаётся заново при каждом рендере. Об этом написано в статье, на которую я ссылаюсь в самом начале)

Я честно засомневался - а вдруг действительно затраты на создание новых функций при каждом рендере не такие уж и крохотные, как мне кажется? Давайте разберёмся

"Задним умом все умные". Вы об этом не знали до публикации статьи и чтения комментов.

Ну зато теперь надеюсь запомнили. Правда для этого пришлось "замусорить" Хабр одной некомпетентной статьей.

А в чем разница?

Разница в точности измерений (гранулярности значений) в некоторых браузерах.

performance.now():

  • в Chrome даёт точность до десятых долей миллисекунды

  • в Node.js - до сотых

  • в Firefox и Safari - до миллисекунды

Date.now() даёт точность до миллисекунды (как и требует спецификация).

Некоторые браузеры, позиционирующие себя как безопасные, раньше на хайпе атак Spectre и Meltdown специально ухудшали точность замеров и performance.now() и Date.now() до пяти миллисекунд. Сейчас не знаю, давно не проверял.

Счётчик performance.now() именно под бенчмарки производительности заточен. Не зависит от часовых поясов, засыпания тредов и прочего, плюс, должен давать бо́льшую точность, если его специально не загрубили для защиты от Side Channel атак.

Обычно мнение о useCallback делится на до и после коммита с оборачиванием компонентов для какой нибудь виртуализации. Чтоб намекнуть людям, что такая проблема как-бы есть, сейчас допиливают React Compiler который автоматически будет это делать.

https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024

  • погружение (честно не знал, что в синтетических ивентах нельзя его перехватывать)

Перехватить синтетическое событие на этапе погружения можно. Это делается с помощью добавления постфикса Capture в атрибут обработчика какого-либо события в JSX.

Например, следующий код перехватывает событие клика на этапе погружения и всплытия:

// ...
<div className="wrapper">
      <div
        className="container"
        onClickCapture={(e) => {
          console.log("Погружение");
        }}
        onClick={(e) => {
          console.log("Всплытие");
        }}
      ></div>
</div>
// ...

Ну и в консоли браузера вывод будет правильный:

Подробнее о SyntheticEvent можно почитать здесь: https://ru.react.js.org/docs/events.html

Рекомендую автору поправить этот момент, т.к. синтетические события на этапе погружения обрабатывать (или перехватывать) можно.

Ну вот, и здесь обманули. Спасибо)

Вы же понимаете, что замеряли скорость работы development-сборки реакта и что в production-сборке результаты измерений будут другими?

В development-сборке есть дополнительные проверки, они занимают дополнительное время, и на этом фоне искомая разница в один вызов useCallback на один ReactElement может быть незаметна. Пример - профайл десяти нажатий на кнопку "Update state" в приложении по вашей ссылке в Chrome. useCallback там есть, но он практически незаметен:

Если речь про StrictMode, то я намеренно его выпилил из песочницы

Во-первых, в песочнице по ссылке из статьи https://codesandbox.io/p/sandbox/usecallback-test-cxxgv7?file=%2Fsrc%2Findex.js%3A14%2C1 StrictMode НЕ выпилен:

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

Во-вторых, речь не столько о StrictMode (да, он может вызывать отдельные части кода по два раза вместо одного, но он это делает одинаково в обоих сравниваемых вариантах, так что привносит одинаковые изменения в замерах), сколько о разнице в сборках React и бесполезности замеров тонких различий скорости в совсем не предназначенной для этого сборке. И вы вполне могли бы нагуглить, о чём речь, самостоятельно: https://www.google.com/search?q=react+development+mode+vs+production. Я и так вкратце об этом написал в предыдущем комментарии, но в интернете можно найти то же самое, описанное более подробно.

Ваша правда, выпиливал в другой песочнице)

Возможно, собеседующий просто не хотел нанимать людей, что умнее его. А то его еще начальниками станут или просто займут его место.
Надо было об этом сообщить руководству, что их подчиненные умышленно набирают неконкурентоспособный персонал.
Зачастую собеседование с гендиректором проще, чем с рядовыми сотрудниками. Он мог бы вас взять, если бы вы смогли объяснить ситуацию.

Ситуация тут уж точно не на стороне автора статьи, он действительно не понимает базовых механизмов хуков, а именно предотвращают ли они создание функции на каждый рендер.

Эх, классика. К сожалению, кроме дефолтных eventLoop/closures/async js на каждом первом собесе Вас еще ждут и другие приключения (помимо стандартных заблуждений насчет пользы повсеместного использования useMemo/useCallback). Собеседующие, которые будут спрашивать про разницу между const/let/var и всецело ожидать ответа в духе "var не нужен, он deprecated, а let и конст это круто" (утрируя). На этом этапе, если рассказать про overhead от обслуживания TDZ, обычно круглые глаза. Да, на проекте девелопер не должен писать вары руками, но это явно не "deprecated мусор" и в теории на определенных проектах можно подумать о транспайле в вары, например на этапе билда проекта.
Вот интересный issue по этой теме - https://github.com/microsoft/TypeScript/issues/52924.
Вообще, мне кажется, каждый для себя должен выделить перечень "ред флагов" на собеседовании, после которых уже можно сделать выводы о компетенции самих собеседующих либо об их подготовке к этому самому собеседованию. Иногда такое ощущение, что перед собесом просто в гугле забивается "топ 20 вопросов по js" и дело с концом.
Автору желаю терпения и в конце концов найти хорошую команду.

Потому что проще обернуть handler в useCallback и забыть о нем, т.к. если джун захочет вашу компоненту разнести и использовать как аргумент для другой молекулы, то он скорее всего забудет сделать эту обертку и его молекула будет ререндериться на любой чих. Так что useCallback просто полезен как предохранитель на огнестреле.

Даже если вы доказали, что useCallback не ускоряет код, то он ее минимум его и не замедляет… А значит лучше использовать везде на случай каких-либо модификаций.

Вы не забудьте что пользовательская функция внутри useCallback будет создаваться новая на каждый рендер вне зависимости от того что находится в массиве зависимостей. Например: useCallback((() => {console.log('Created new function'); return () => {};})(), []). Этот хук нужен лишь для того чтобы хранить указатель на функцию. Именно поэтому я в процессе экспериментов переосмыслил Реакт без использования хуков и с гибким рендером, что вылилось в небольшую библиотечку https://github.com/fusorjs/dom

Sign up to leave a comment.

Articles