Что такое проблема №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« Родитель против Отрисовки »