React钩子useEffect仅在更新时有效吗?

koo*_*koo 22 reactjs react-hooks

如果要限制useEffect仅在组件安装时运行,则可以添加useEffectwith的第二个参数[]

useEffect(() => {
  // ...
}, []);
Run Code Online (Sandbox Code Playgroud)

但是,如何使useEffect该组件仅在组件被更新(除了初始安装)的那一刻才运行?

Shu*_*tri 43

如果要运行useEffect以仅在除初始安装之外的更新上运行,则可以利用useRef跟踪带有useEffect第二个参数的initialMount 。

const isInitialMount = useRef(true);

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

  • 事实上,这个问题列在[**React FAQ**](https://reactjs.org/docs/hooks-faq.html)和[**这里**](https://reactjs.org/ docs/hooks-faq.html#can-i-run-an-effect-only-on-updates)这里明确表示这是正确的方法。 (6认同)
  • 非常聪明的解决方案 (4认同)
  • 我认为严格模​​式由于双重渲染而打破了这一点 - https://codesandbox.io/s/sharp-feynman-9qs09s (3认同)
  • 就是这个。我建议首先了解 `useRef`,然后使用 `react-use` 中的 [`useUpdateEffect`](https://github.com/streamich/react-use/blob/master/docs/useUpdateEffect.md)。 (2认同)

San*_*ina 7

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
};
Run Code Online (Sandbox Code Playgroud)

  • 为什么清理时需要设置`isInitialMount.current = true;`?这真的有必要吗? (3认同)
  • 伟大的。为什么我们使用useIsMounted()?为什么我们在清理时不应该使用 if (!isInitialMount.current) ? (2认同)

Mar*_*mpa 6

我真的很喜欢Shubham的回应,所以我把它做成了自定义的Hook

/**
 * A custom useEffect hook that only triggers on updates, not on initial mount
 * Idea stolen from: /sf/answers/3855307291/
 * @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 {
      effect();
    }
  }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
}
Run Code Online (Sandbox Code Playgroud)

  • 难道我们不应该“返回effect();”而不是仅仅调用“effect();”以便我们尊重清理功能吗? (2认同)

Zaw*_*wad 6

较短的一个

const [mounted, setMounted] = useRef(false)

useEffect(() => {
  if(!mounted) return setMounted(true)
  ...
})
Run Code Online (Sandbox Code Playgroud)

反应钩子解决方案

export const useMounted = () => {
  const mounted = useRef(false)

  useEffect(() => {
    mounted.current = true
    return () => {
      mounted.current = false
    }
  }, [])

  return () => mounted.current
}
Run Code Online (Sandbox Code Playgroud)

用法

const Component = () => {
  const mounted = useMounted()

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

Run Code Online (Sandbox Code Playgroud)


awo*_*ngh 5

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

  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]);
Run Code Online (Sandbox Code Playgroud)