【问题标题】:React hooks useEffect only on update?反应挂钩 useEffect 仅在更新时?
【发布时间】:2019-07-31 05:13:08
【问题描述】:

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

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

但是我们怎样才能让useEffect只在组件更新的那一刻才运行呢?

【问题讨论】:

标签: reactjs react-hooks


【解决方案1】:

如果您希望 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
  }
});

【讨论】:

  • 我认为useRef只能用于DOM操作。谢谢!
  • 非常聪明的解决方案
  • 其实这个问题列在React FAQhere这里明确说这是正确的方法。
  • 就是这样。我建议先了解useRef,然后从react-use 使用useUpdateEffect
  • 简单又好用:)
【解决方案2】:

我真的很喜欢 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 规则?
  • 我找不到使它符合规则的方法,所以我禁用了它。我将从答案中删除它,因为它不相关。
  • 对于打字稿,我不得不更改为`useUpdateEffect(效果:函数,依赖项:任何[] = [])`
  • 我们不应该return effect(); 而不是仅仅调用effect(); 以便我们尊重清理功能吗?
【解决方案3】:

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)?
  • 清理时为什么要设置isInitialMount.current = true;?真的有必要吗?
【解决方案4】:

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

  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
【解决方案5】:

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

【讨论】:

    【解决方案6】:

    较短的一个

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

    【讨论】:

    • return 不是用于卸载时的清理吗?
    【解决方案7】:

    要使自定义钩子符合钩子规则,您不需要实际传递依赖项,只需使用 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>;
    }
    

    【讨论】:

      【解决方案8】:

      使用useEffect清理功能,而不使用空数组作为第二个参数:

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

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

      【讨论】:

      • 我正在无限循环
      • 组件卸载时执行react cleanup函数。 OP 正在询问如何在“更新”上执行代码,或者换句话说,在除初始渲染之外的每个渲染上执行代码,初始渲染在安装时运行。
      猜你喜欢
      • 1970-01-01
      • 2021-07-24
      • 1970-01-01
      • 2019-12-27
      • 2021-04-23
      • 1970-01-01
      • 2022-11-22
      • 1970-01-01
      • 2019-11-15
      相关资源
      最近更新 更多