Что такое проблема №14099 в репозитории React и как она влияет на вас?

Зачем вообще нужен useCallback?

В официальной документации useCallback сказано:

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

В следующем примере PureHeavyComponent будет повторно отображать каждый раз, когда компонент Parent повторно отображается, хотя PureHeavyComponent является чистым потому что предыдущий props.onClick !== новый props.onClick, потому что новая onClick функция создается при каждом рендеринге Parent.

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

Здесь в игру вступает наш useCallback

Мы заключаем onClick в useCallback, чтобы гарантировать, что PureHeavyComponent будет отображаться только один раз.

useCallback кэширует («запоминает») первую функцию, переданную ему при первом рендеринге Parent, и всегда передает эту же функцию PureHeavyComponent.

Поскольку PureHeavyComponent является чистым и все его свойства в этом случае одинаковы, он больше не обрабатывается повторно.

Проблема с useCallback

Если любое из deps из useCallback изменяется, handleClick становится «недействительным», что означает, что он больше не будет использовать мемоизированное значение, а будет использовать новое, которое ему было передано.

Рассмотрим следующий код:

Если родительский рендеринг выполняется повторно по какой-либо причине, кроме нажатия на «увеличить счетчик», handleClick не будет признан недействительным, и все будет работать должным образом.

Но всякий раз, когда вы нажимаете кнопку «увеличить счетчик», поскольку count изменяется, handleClick становится недействительным и PureHeavyComponent будет повторно обработан.

В этом случае, поскольку этот рендеринг «тяжелый», он может вызвать задержку и проблемы с производительностью, поскольку он замедлит время отклика приложения на щелчок.

Именно в этом заключается проблема useCallback () слишком часто делает недействительной на практике # 14099.

Обходные пути

Компонент класса

Помните «доисторические времена», когда мы использовали «Компоненты класса»?

Это прекрасно работает. this.handleClick всегда одна и та же функция.

useEventCallback

Этот хук и его недостатки также обсуждаются в официальных документах React.

useEventCallback делает кое-что умное. Он сохраняет последнюю переданную ему функцию на useRef и предоставляет мемоизированную функцию, которая вызывает сохраненную функцию со всеми соответствующими аргументами.

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

Между прочим, странный и редко используемый синтаксис (0, ref.current)(args) здесь использует оператор запятой, чтобы гарантировать, что this функции не ref, чтобы не меньше, чем пользователь ловушки по ошибке запутался с ref.

Вот песочница, демонстрирующая проблему и useEventCallback обходной путь.

Возможное исправление ошибки в будущем

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

Можно даже полностью удалить второй аргумент (deps) из ловушки и просто продолжать вызывать последнюю полученную функцию.

Я считаю, что такое решение сделало бы крючок намного более мощным, безопасным и простым для понимания.

Резюме

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

Статьи по теме, которые могут быть вам полезны:

Почему вы выпустили версию 4 для рендеринга!

React Element's« Родитель против Отрисовки »