Pull to refresh

Comments 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 написано следующее:


Передайте «создающую» функцию и массив зависимостей.

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

https://ru.reactjs.org/docs/hooks-reference.html#usememo

В чем ошибка-то?
A, понятно. В TS оно без массива зависимостей вообще не скомпилится.

React.useCallback используется без указания зависимостей, значит, increment, decrement, reset будут создаваться новые при каждом перерендере Component

A, понятно. В TS оно без массива зависимостей вообще не скомпилится.

TS тут не причём. Использование useCallback и useMemo без 2-го аргумента просто не имеет смысла.

И это отражено в сигнатуре useCallback в @types/react


function useCallback<T extends (...args: any[]) => any>(
  callback: T, deps: DependencyList
): T;
Есть мнения, что при начальном написании кода использовать useCallback на каждый чих не рекомендуется (типа преждевременная оптимизация).

Я конечно понимаю, что это перевод. Но пользуясь случаем хочу спросить у знающих людей:
как тестировать хоть сколько-нибудь сложные custom Hooks? Например, с контекстом, асинхронными эффектами или third-party хуками.

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

Спасибо тебе, добрый человек!

Не могу не добавить, что 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.

Sign up to leave a comment.