Как стать автором
Поиск
Написать публикацию
Обновить

Комментарии 13

Никто не говорит "функции обратного вызова" - все говорят "колбэк".

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

Но на каждый ререндер вы всё равно создаёте и прокидываете в него новую функцию

В обычном JS - да, но в JSX (с переводом в AST) - новая функция не создаётся при каждом рендере, если зависимости не менялись.

Если коротко, то JSX - это просто формат файла, который к JS имеет очень косвенное отношение. Синтаксис похож на JS, но он не работает как JS. Из него сначала строится абстрактное синтаксическое дерево (AST), потом отдельный механизм в React обрабатывает каждую часть кода отдельно, и на основе этого уже строится JS. Поэтому привычные законы JS с ним не работают, там происходит очень много магии под капотом при компиляции.

Я в комментарии ниже подробнее описал, не буду здесь дублировать.

Боже, вы о чем jsx компилируется в js. Пирсинг АСТ тут не причем, он существует только на моменте сборки, но никак на моменте исполнения. Все что делает для реакта плагин Бабеля это заменяет весь xml, на вызовы функции h. Полное непонимание темы.

При последующих рендерах React будет:

  • проверять, изменились ли значения в массиве зависимостей;

  • если ничего не изменилось, он возвращает тот же экземпляр функции, что и при предыдущем рендере;

  • если что-то изменилось, React создает новый экземпляр функции и возвращает его.

У вас явно пробелы в понимании работы useCallback. Всегда будет создаваться новая анонимная функция, после чего сравнятся зависимости в массиве, и уже потом вам вернётся либо ранее сохранённая функция с прежней ссылкой, либо новая, и она же сохранится вместо старой, для последующих рендеров. Это даже не реакт, а банальное понимание того, как функции и замыкания работают в принципе. useCallback это та же функция, каким термином её ни назови

Как уже написали выше, всё "руководство" по использованию данного хука сводится к одному короткому абзацу. Всё остальное вода, которую лили на хабре до вас, и будут лить после

Всегда будет создаваться новая анонимная функция

...

Это даже не реакт, а банальное понимание того, как функции и замыкания работают в принципе

Простите, но вы не правы. Ваши доводы логичны, но вы упускаете одну важную деталь, из-за чего все ваши выводы неверны.

useCallback запускается не в чистом JS, а в JSX. А если быть точнее, проходит через транспиляцию в AST (абстрактное синтаксическое дерево), и далее React работает уже с ним. И это касается не только дерева элементов в return, но и всего кода внутри функции компонента (включая и все хуки).

Достаточно простое доказательство этого - это новая версия React, в которой, как пообещали разработчики, useCallback писать не потребуется, ко функциям внутри компонента будет автоматически применяться мемоизация на основании анализа кода компилятором.

Также вы можете посмотреть скомпилированный в JS код, и вы там увидите, что функция из useCallback на самом деле находится за пределами функции компонента и перезаписывается там же (советую включить в React компиляцию компонентов по отдельным бандлам, чтобы проще было читать код, не было лишней логики).

Я рекомендовал бы вам прочитать статью про работу AST (на ней работает большинство современных js-фреймворков, типа React, Vue, Svelte и другие): https://habr.com/ru/companies/ruvds/articles/415269/

Простите, но вы не правы. Ваши доводы логичны, но вы упускаете одну важную деталь, из-за чего все ваши выводы неверны

Да нет, скорее вы упорно не хотите понимать, как работает javascript) jsx или нет - это всё ещё JS. и в моём мире функция useCallback с входными аргументами X и Y(где X анонимная функция и Y массив зависимостей) в момент вызова уже получает на вход два этих аргумента. То есть она не может вызваться только со вторым аргументом, а когда тот не прошёл проверку сказать "ну лааадно, давайте сюда первый, запишу его вместо старой функции". Понимаете? Как только useCallback был вызван, он уже получил на вход всё необходимое, в том числе новую анонимную функцию, на каждый рендер. Достаточно заглянуть в исходники;)

Достаточно простое доказательство этого - это новая версия React, в которой, как пообещали разработчики, useCallback писать не потребуется, ко функциям внутри компонента будет автоматически применяться мемоизация на основании анализа кода компилятором.

Доказательство простите чего? Того, что анонимная функция не будет создаваться каждый раз? Не вижу связи абсолютно. Если вы не читали о том, как это будет работать - оно просто само будет решать, что обернуть в useCallback(или в прочие мемоизации), а что нет. Не представляю, как это может что-либо доказывать)

Простите, вы оказались абсолютно правы. Оказалось, что использование глубокой пересборки кода с помощью AST - это было только в моих мечтах. Насмотревшись на магию SvelteJS и аналогичных решений с построчной обработкой кода, я предполагал, что и в ReactJS эту же магию используют в компиляторе, а-ля:

const cb = useCallback(() => {
  console.log(`${someFirstDynamicValue}-${someSecondDynamicValue}`);
}, [someFirstDynamicValue, someSecondDynamicValue]);

->

callbacksMap[123] =
  someFirstDynamicValue === callbacksMap[123].dependencies[0] &&
  someSecondDynamicValue === callbacksMap[123].dependencies[1]
    ? callbacksMap[123]
    : () => {
      console.log(`${someFirstDynamicValue}-${someSecondDynamicValue}`);
    };

Но в финале всё оказалось намного прозаичнее. Действительно, мы при каждом рендере создаём новую функцию внутри useCallback . Как описывают это сами разработчики ReactJS:

The default behavior of useCallback is rather naïve but it is very easy to predict, and you always know that you're gonna "see" fresh values there.

Наверное, это правильно с их стороны. Но в этом случае я теперь 100 раз подумаю, нужно ли мне использовать useCallback или нет.

Вот пример, как проблему с пересозданием функции можно решить (может, кому-то это пригодится):

function useOptimizedCallback<T extends (...args: any[]) => any>(fn: T, deps: readonly unknown[]): T {
  const ref: any = useRef(fn);

  // we copy a ref to the callback scoped to the current state/props on each render
  useLayoutEffect(() => {
    ref.current = fn;
  }, deps);

  return useCallback(
    (...args: any[]) => ref.current.apply(void 0, args),
    []
  ) as T;
}

@winkyBrain@adminNiochenСпасибо, что мотивировали пойти и собственноручно разобраться в этом! А то бы до последнего в это верил, как дурак

Очень крутое погружение в тему) прям лайк. я тоже усомнился и побежал проксировать useCallback, чтобы убедиться, что ещё до вызова самого useCallback анонимная функция уже существует в массиве аргументов apply. Так и есть)

Я рекомендовал бы вам прочитать статью про работу AST (на ней работает большинство современных js-фреймворков, типа React, Vue, Svelte и другие)

Тоже странное заявление, ведь AST используется в самих языках программирования) в том числе в JS. Очевидно, что всё, что написано на таком языке, на этапе парсинга превратится в AST. При чём тут фрейвморки - остаётся только гадать)

C AST может работать транспилятор из jsx в js (например, Babel или TypeScript), но не сам React. React Compiler, добавляющий оптимизации, поставляется как плагин к транспилятору.

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      console.log(`Count is: ${count}`);
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  return <button onClick={() => setCount(count + 1)}>Increment</button>;
}

И как здесь может помочь useCallback?

О-хо-хо. А вам в панамку не напихали ещё за описание работы хука? При использовании реакт всегда! создаёт новый экземпляр функции и либо отбрасывает его, если зависимости не поменялись, либо возвращает старую ссылку на функцию.
"On the following renders, React will compare the dependencies with the dependencies you passed during the previous render. If none of the dependencies have changed (compared with Object.is), useCallback will return the same function as before. Otherwise, useCallback will return the function you passed on this render"

Зарегистрируйтесь на Хабре, чтобы оставить комментарий