Comments 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 к этому самописному хуку?
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 медленно работает, а вот насколько? Если такие тяжёлые объекты на вход идут, то как же их рендерить?
React: разрабатываем кастомный useEffect