React 钩子 useEffect 仅在更新时?

IT技术 reactjs react-hooks
2021-03-28 04:43:47

如果我们想限制useEffect只在组件挂载时运行,我们可以添加useEffectwith 的第二个参数[]

useEffect(() => {
  // ...
}, []);

但是我们如何才能让useEffect除了初始挂载之外的组件更新的那一刻才运行呢?

6个回答

如果您希望 useEffect 仅在除初始安装之外的更新上运行,您可以使用 ofuseRef来跟踪 initialMountuseEffect而不带第二个参数。

const isInitialMount = useRef(true);

useEffect(() => {
  if (isInitialMount.current) {
     isInitialMount.current = false;
  } else {
      // Your useEffect code here to be run on update
  }
});
我认为 useRef 只能用于 DOM 操作。谢谢!
2021-05-25 04:43:47
事实上,这个问题列在React FAQ 中这里明确表示这是正确的方法。
2021-06-13 04:43:47
就是这个。我建议useRef了解,然后使用useUpdateEffectfrom react-use
2021-06-14 04:43:47
简单且非常有用:)
2021-06-14 04:43:47
非常聪明的解决方案
2021-06-19 04:43:47

我真的很喜欢 Shubham 的回应,所以我做了一个定制的 Hook

/**
 * 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 规则?
2021-05-26 04:43:47
对于typescript,我不得不更改为 `useUpdateEffect(effect: Function, dependencies: any[] = [])`
2021-06-02 04:43:47
我找不到让它符合规则的方法,所以我禁用了它。我将从答案中删除它,因为它不相关。
2021-06-03 04:43:47
我们return effect();应该只是调用,effect();以便我们尊重清理功能吗?
2021-06-04 04:43:47

Shubham 和 Mario 都提出了正确的方法,但是代码仍然不完整,没有考虑以下情况。

  1. 如果组件卸载,它应该重置它的标志
  2. 传递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) ?
2021-05-24 04:43:47
为什么我们isInitialMount.current = true;在清理的时候需要设置那真的有必要吗?
2021-05-24 04:43:47

您可以通过将状态设置为非布尔初始值(如空值)来解决它:

  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.
2021-05-27 04:43:47

从 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.log('Appstaatus', appStatus, timeSpentBG, newHeatingTimer)
         }
     }
  }, [appStatus])