Если мы хотим ограничить запуск useEffect только при монтировании компонента, мы можем добавить второй параметр useEffect с [].
useEffect(() => {
// ...
}, []);
Но как сделать так, чтобы useEffect запускался только в момент обновления компонента, кроме начального монтирования?
Ну... решение ниже выглядит более точным и понятным.





Если вы хотите, чтобы useEffect запускался только для обновлений, кроме начального монтирования, вы можете использовать useRef для отслеживания начального монтирования с useEffect без второго параметра.
const isInitialMount = useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
// Your useEffect code here to be run on update
}
});
Я думал, что useRef можно использовать только для манипуляций с DOM. Спасибо!
очень умное решение
На самом деле этот вопрос указан в Ответить на часто задаваемые вопросы и здесь здесь прямо сказано, что это правильный путь.
Это оно. Я предлагаю сначала понять useRef, а затем использовать useUpdateEffect из react-use.
Просто и очень полезно :)
Отличное решение.
Мне очень нравится ответ Шубхама, поэтому я сделал кастомный хук.
/**
* A custom useEffect hook that only triggers on updates, not on initial mount
* @param {Function} effect
* @param {Array<any>} dependencies
*/
export default function useUpdateEffect(effect, dependencies = []) {
const isInitialMount = useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
return effect();
}
}, dependencies);
}
Почему вы отключаете правило eslint?
Я не смог найти способ сделать его совместимым с правилом, поэтому отключил его. Я уберу это из ответа, так как не имеет значения.
Для машинописного текста мне пришлось изменить на `useUpdateEffect (эффект: функция, зависимости: любой [] = [])`
Разве мы не должны return effect(); вместо того, чтобы просто вызывать effect();, чтобы выполнить функцию очистки?
И Шубхам, и Марио предлагают правильный подход, однако код все еще неполный и не рассматривает следующие случаи.
effect может иметь возвращаемую из нее функцию очистки, которая никогда не будет вызвана.Ниже приводится более полный код, который охватывает два вышеперечисленных отсутствующих случая:
import React from 'react';
const useIsMounted = function useIsMounted() {
const isMounted = React.useRef(false);
React.useEffect(function setIsMounted() {
isMounted.current = true;
return function cleanupSetIsMounted() {
isMounted.current = false;
};
}, []);
return isMounted;
};
const useUpdateEffect = function useUpdateEffect(effect, dependencies) {
const isMounted = useIsMounted();
const isInitialMount = React.useRef(true);
React.useEffect(() => {
let effectCleanupFunc = function noop() {};
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
effectCleanupFunc = effect() || effectCleanupFunc;
}
return () => {
effectCleanupFunc();
if (!isMounted.current) {
isInitialMount.current = true;
}
};
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
Здорово. Почему мы использовали useIsMounted()? Почему мы не должны использовать if (!isInitialMount.current) при очистке?
Зачем нам нужно ставить isInitialMount.current = true; при уборке? Это действительно необходимо?
Другим подходом может быть вызов useEffect дважды или более, в зависимости от «проблемы». См. ответ Руди Наппе из комментариев ниже сообщения в блоге:
https://dev.to/savagepixie/how-to-mimic-componentdidupdate-with-react-hooks-3j8c
В двух словах:
useEffect(concern1, [...]);
useEffect(concern2, [...]);
...
Для каждого экземпляра useEffect передайте другой аргумент массива, который вы пытаетесь просмотреть.
Вы можете обойти это, установив для состояния нелогическое начальное значение (например, нулевое значение):
const [isCartOpen,setCartOpen] = useState(null);
const [checkout,setCheckout] = useState({});
useEffect(() => {
// check to see if its the initial state
if ( isCartOpen === null ){
// first load, set cart to real initial state, after load
setCartOpen( false );
}else if (isCartOpen === false){
// normal on update callback logic
setCartOpen( true );
}
}, [checkout]);
Этот код не будет работать, потому что isCartOpen не соблюдается и всегда будет null.
Принял помощь от ответа Subham Этот код будет выполняться только для обновления конкретного элемента, а не при каждом обновлении и не при первоначальном монтировании компонента.
const isInitialMount = useRef(true); //useEffect to run only on updates except initial mount
//useEffect to run only on updates except initial mount
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
if (fromScreen!='ht1' && appStatus && timeStamp){
// let timeSpentBG = moment().diff(timeStamp, "seconds");
// let newHeatingTimer = ((bottomTab1Timer > timeSpentBG) ? (bottomTab1Timer - timeSpentBG) : 0);
// dispatch({
// type: types.FT_BOTTOM_TAB_1,
// payload: newHeatingTimer,
// })
// console.info('Appstaatus', appStatus, timeSpentBG, newHeatingTimer)
}
}
}, [appStatus])
const [mounted, setMounted] = useRef(false)
useEffect(() => {
if (!mounted) return setMounted(true)
...
})
export const useMounted = () => {
const mounted = useRef(false)
useEffect(() => {
mounted.current = true
return () => {
mounted.current = false
}
}, [])
return () => mounted.current
}
const Component = () => {
const mounted = useMounted()
useEffect(() => {
if (!mounted()) return
...
})
}
Разве возврат не используется для очистки при размонтировании?
Используйте Функция очисткиuseEffect без использования пустого массива в качестве второго параметра:
useEffect(() => {
return () => {
// your code to be run on update only.
}
});
Я получаю бесконечный цикл
Функция очистки реакции выполняется, когда компонент размонтируется. ОП спрашивает, как выполнять код при «обновлении» или, другими словами, при каждом рендеринге, кроме начального рендеринга, который запускается при монтировании.
Чтобы сделать пользовательский хук совместимым с правилами хуков, вам не нужно фактически передавать зависимости, просто оберните функцию эффекта с помощью useCallback
function useEffectOnUpdate(callback) {
const mounted = useRef();
useEffect(() => {
if (!mounted.current) {
mounted.current = true;
} else {
callback();
}
}, [callback]);
};
function SomeComponent({ someProp }) {
useEffectOnUpdate(useCallback(() => {
console.info(someProp);
}, [someProp]));
return <div>sample text</div>;
}
Возможный дубликат Сделать так, чтобы хук React useEffect не запускался при начальном рендеринге