React 挂钩仅在更新时使用 Effect?

React hooks useEffect only on update?

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

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

但是如何让useEffect变成运行只在组件更新的时候,除了初始挂载?

如果您希望 useEffect 运行 仅在除初始挂载之外的更新中使用,您可以使用 useRef 来跟踪 initialMount useEffect 而无需第二个参数。

const isInitialMount = useRef(true);

useEffect(() => {
  if (isInitialMount.current) {
     isInitialMount.current = false;
  } else {
      // Your useEffect code here to be run on update
  }
});

我非常喜欢 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);
}

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
};

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

  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]);

从 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])

更短的一个

const [mounted, setMounted] = useRef(false)

useEffect(() => {
  if(!mounted) return setMounted(true)
  ...
})

React Hook 解决方案

挂钩

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
    ...
  })
}

使用 useEffectCleanup 函数,而不使用空数组作为第二个参数:

useEffect(() => { 
  return () => {
  // your code to be run on update only.
  }
});

您可以使用另一个 useEffect(第二个参数为空数组)进行初始挂载,将代码放在其主要函数中。

要使自定义钩子符合钩子规则,您不需要实际传递依赖项,只需使用 useCallback

包装您的效果函数
function useEffectOnUpdate(callback) {
  const mounted = useRef();

  useEffect(() => {
    if (!mounted.current) {
      mounted.current = true;
    } else {
      callback();
    }
  }, [callback]);
};

function SomeComponent({ someProp }) {
  useEffectOnUpdate(useCallback(() => {
    console.log(someProp);
  }, [someProp]));

  return <div>sample text</div>;
}