Комментарии 15
const useSelection = () => {
const [selected, setSelected] = React.useState()
const select = React.useCallback(id => setSelected(/** ... */), [
selected,
setSelect
])
return [selected, select]
}
function Component() {
const [selected, select] = useSelection()
return <List selectedIds={selected} onSelect={id => select(id)} />
}
А если буквально ещё 10 секунд подумать и не спешить публиковать статью? Правильно, получится:
return <List selectedIds={selected} onSelect={select} />
const increment = React.useCallback(() => dispatch(CounterActionTypes.Increment));
const decrement = React.useCallback(() => dispatch(CounterActionTypes.Decrement));
const reset = React.useCallback(() => dispatch(CounterActionTypes.Reset));
Зачем здесь React.useCallback()?
Иначе будут создаваться новые increment, decrement, reset при каждом перерендере Component. И ниже по дереву не будут работать всякие React.memo
/ React.useMemo
.
Кажется разобрался, но здесь все равно ошибка.
В документации к useMemo написано следующее:
Передайте «создающую» функцию и массив зависимостей.
…
Если массив не был передан, новое значение будет вычисляться при каждом рендере.
В чем ошибка-то?
A, понятно. В TS оно без массива зависимостей вообще не скомпилится.
React.useCallback используется без указания зависимостей, значит, increment, decrement, reset будут создаваться новые при каждом перерендере Component
A, понятно. В TS оно без массива зависимостей вообще не скомпилится.
TS тут не причём. Использование useCallback
и useMemo
без 2-го аргумента просто не имеет смысла.
Я конечно понимаю, что это перевод. Но пользуясь случаем хочу спросить у знающих людей:
как тестировать хоть сколько-нибудь сложные custom Hooks? Например, с контекстом, асинхронными эффектами или third-party хуками.
Ну т.к. они разработаны для работы в контексте хуков, то, полагаю, даже модульные тесты будут похожи на интеграционные: нужно возвести окружение работающего react, написать тестовый компонент, использующий хук специальным для тестируемости образом, и уже для него писать тест.
Есть уже готовая удобная обертка @testing-library/react-hooks
Не могу не добавить, что setter-ы из useState, и dispatch из useReducer нет резона заносить в useCallback dependencies. Ибо они статичны для компонента.
Ещё можно посмотреть в сторону hooks.macro
, чтобы не писать их руками. Правда будут сложности (большие) с тем, чтобы использовать их одновременно с TypeScript (хотя это и возможно).
И ещё 1 момент. Наверное на целую статью тянет, но попробую описать кратко. По большому счёту обычно не нужно пересоздавать callback-и при изменении их зависимостей. Это приходится делать, чтобы избегать лишних rerender-ов, путём, скажем, оборачивания их в useCallback
. Но если крепко задуматься, то быстро приходишь к выводу, что в принципе достаточно того, чтобы:
- callback не изменялся от render-а к render-у
- callback имел доступ к самым свежим данным
Этого можно достичь посредством useRef
.
Следите за руками:
const { current: ref } = useRef({});
Object.assign(ref, { dep1, dep2, dep3 });
const callback = useCallback(() => {
whatever(ref.dep1, ref.dep2, ref.dep3);
}, []);
Что здесь происходит? По сути мы создаём callback при первом рендере и используем его пока компонент не помрёт. Он всегда один и тот же (из-за useCallback + []
). А доступ к свежим данным обеспечиваем за счёт useRef
.
На самом деле я не рекомендую так везде писать, т.к. это ну слишком дофига кода ради, во имя ничего… Но если у вас есть какие-то шибко сложные UI хуки, где это уже по сути большое дерево и оно ещё и требовательно к производительности (ну например какие-нибудь виджеты карт), то это может быть актуальным. Тоже самое и для каких-то совсем general-вещей в проекте. Скажем у вас есть какой-нибудь useRequest
который вы используете буквально везде. Сделав в нём так, вы сильно упростите себе debug.
5 практических рекомендаций по использованию React-хуков в продакшне