Обновить

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

Не делайте так. Это прям глобальный выстрел в ногу и идет против всей идеологии реакта. В итоге вы получите эффект с неочевидным и не прямым жизненным циклом.

useEffect должен зависеть от тех свойств, изменение которых приводит к пересчету логики внутри эффекта. Следование этому правилу убирает все эти странные советы:

// плохо, даже если у вас трижды useDeepEffect
useEffect(() => {
   fetchData(data.id)
}, [data]);

// хорошо
useEffect(() => {
   fetchData(data.id);
}, [data.id]); // заметьте прямую зависимость от id 

В случае с useDeepEffect у вас перезагрузка будет происходить даже если поменялись нерелевантные данные. Если она так и задумана -- объявляйте больше зависимостей напрямую. Ну или у вас уже где-то глубоко архитектурная проблема.

PS ваш useDeepEffect, в общем случае, будет приводить к двойной перерисовки компонента, что может плохо сказаться на производительности. Ну и большой шанс, что три эффекта с useRef и setState будет гораздо медленнее, чем логика, которая в эффекте.


PPS

React: разрабатываем кастомный useEffect

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

Не перегибайте палку.


useEffect(() => {
   fetchData(data.id)
}, [data]);

Это нормальный код (если человек знает что делает). Это же не useMemo или useCallback. Хук useEffect он куда более, как бы это сказать, ориентированный на бизнес-логику. Его deps это совершенно не обязательно те поля, которые задействованы внутри. Он ведь — side effect. Side effect-ы это муть мутная и зависят от конкретной бизнес-задачи. В большинстве случаев всё просто и deps по бизнес-логике совпадают с deps задействованными. Но не стоит обманываться. В deps могут быть вообще незадействованные поля, так же как могут не быть задействованные. Это "ручная" работа. Линтеры на useEffect имхо просто мусор.


В итоге вы получите эффект с неочевидным и не прямым жизненным циклом.

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


Т.е. в общем и целом я согласен что deps надо указывать как можно более прицельно. Но не стоит забывать что эффекты это эффекты, и это триггер эффекта. Штука к которой надо относиться очень серьёзно и исходить из реалий бизнес-логики, моделей данных, окружения у компонента и т.д… Просто подход — что заюзал, то и занёс в deps это самообман.

Вы, конечно же, правы. Но я считаю, что опустив случаи микрооптимизаций (там много чего идет в ход, что не react-way, например, когда нужно делать onscroll), с загрузкой / перезагрузкой данных такое возникает обычно когда данные обновились, а их айди - нет. Я бы сказал что, начиная с какой-то сложности, лучше всё таки переходить на что-то более подходящее, чем эффекты: redux, mobx, rxjs, react query, в конце концов. Эффекты с непрямыми зависимостями рано или поздно начнут вызываться не тогда, когда вы этого хотите, и победить это может быть проблематично.

Обратите внимание: все операции можно выполнять в одном useEffect, но мы будем придерживаться принципа единственной ответственности (single responsibility).

Плюсы от SRP тут совсем неочевидны, зато будет лишний ререндер.

Поправил:

function useDeepEffect(cb: VoidFunction, deps: unknown[]) {
    const prevDeps = usePrevious(deps);
    const firstRender = useRef(true);

    useEffect(() => {
        if (firstRender.current || !prevDeps) {
            firstRender.current = false;
        } else {
            if (areEqual(deps, prevDeps)) {
                return;
            }
        }

        cb();
    }, [deps, cb, prevDeps]);
}

теперь линтер не ругается.

Кстати, можно ли прикрутить react-hooks/exhaustive-deps к этому самописному хуку?

можно ли прикрутить react-hooks/exhaustive-deps к этому самописному хуку?

сам спросил, сам ответил: additionalHooks в конфиге

useDeepEffect пишется гораздо проще путём глубокой мемоизации его зависимостей.

import { useRef, useEffect, DependencyList } from 'react'

function useDeepCompareDeps(deps: DependencyList | undefined): DependencyList | undefined {
  const ref = useRef(deps)
  if (!isEqual(ref.current, deps)) ref.current = deps
  return ref.current
}

const useDeepEffect: typeof useEffect = (fn, deps) => useEffect(fn, useDeepCompareDeps(deps))

Пишем кастомный useEffect:

function useCustomEffect(a, b) {
    return useEffect(a, b);
}

Profit.

Интересная тема. Столкнулся  с необходимостью подобного хука когда пробовал использовать mobx. Так там нужно было вызывать toJS для "глупых" компонентов. И в итоге пришел к выводу что легче всем этим не заниматься, а ориентироваться на равенство по ссылке из-за неочевидности использования дальнейших решений(кастомные хуки, мемоизации, юзколбеки). Показалось легче уж из того же редакса с помощью реселекта получать один и тот же объект, но оставаться в одной идеологии на всем проекте.
Ещё вопрос, написано что stringify медленно работает, а вот насколько? Если такие тяжёлые объекты на вход идут, то как же их рендерить?

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

Информация

Сайт
timeweb.cloud
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия
Представитель
Timeweb Cloud