Javascript 使 React useEffect 钩子不在初始渲染上运行

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/53253940/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 05:05:25  来源:igfitidea点击:

Make React useEffect hook not run on initial render

javascriptreactjsreact-hooks

提问by Yangshun Tay

According to the docs:

根据文档:

componentDidUpdate()is invoked immediately after updating occurs. This method is not called for the initial render.

componentDidUpdate()在更新发生后立即调用。初始渲染不会调用此方法。

We can use the new useEffect()hook to simulate componentDidUpdate(), but it seems like useEffect()is being ran after every render, even the first time. How do I get it to not run on initial render?

我们可以使用新的useEffect()钩子来模拟componentDidUpdate(),但似乎useEffect()每次渲染后都会运行,即使是第一次。如何让它不在初始渲染上运行?

As you can see in the example below, componentDidUpdateFunctionis printed during the initial render but componentDidUpdateClasswas not printed during the initial render.

正如您在下面的示例中看到的那样,componentDidUpdateFunction在初始渲染期间打印,但componentDidUpdateClass在初始渲染期间未打印。

function ComponentDidUpdateFunction() {
  const [count, setCount] = React.useState(0);
  React.useEffect(() => {
    console.log("componentDidUpdateFunction");
  });

  return (
    <div>
      <p>componentDidUpdateFunction: {count} times</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click Me
      </button>
    </div>
  );
}

class ComponentDidUpdateClass extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  componentDidUpdate() {
    console.log("componentDidUpdateClass");
  }

  render() {
    return (
      <div>
        <p>componentDidUpdateClass: {this.state.count} times</p>
        <button
          onClick={() => {
            this.setState({ count: this.state.count + 1 });
          }}
        >
          Click Me
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <div>
    <ComponentDidUpdateFunction />
    <ComponentDidUpdateClass />
  </div>,
  document.querySelector("#app")
);
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>

回答by Tholle

We can use the useRefhook to store any mutable value we like, so we could use that to keep track of if it's the first time the useEffectfunction is being run.

我们可以使用useRef钩子来存储我们喜欢的任何可变值,因此我们可以使用它来跟踪useEffect函数是否第一次运行。

If we want the effect to run in the same phase that componentDidUpdatedoes, we can use useLayoutEffectinstead.

如果我们希望效果在相同的阶段运行componentDidUpdate,我们可以使用useLayoutEffect

Example

例子

const { useState, useRef, useLayoutEffect } = React;

function ComponentDidUpdateFunction() {
  const [count, setCount] = useState(0);

  const firstUpdate = useRef(true);
  useLayoutEffect(() => {
    if (firstUpdate.current) {
      firstUpdate.current = false;
      return;
    }

    console.log("componentDidUpdateFunction");
  });

  return (
    <div>
      <p>componentDidUpdateFunction: {count} times</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click Me
      </button>
    </div>
  );
}

ReactDOM.render(
  <ComponentDidUpdateFunction />,
  document.getElementById("app")
);
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>

回答by Mehdi Dehghani

You can turn it into custom hooks, like so:

您可以将其转换为自定义 hooks,如下所示:

import React, { useEffect, useRef } from 'react';

const useDidMountEffect = (func, deps) => {
    const didMount = useRef(false);

    useEffect(() => {
        if (didMount.current) func();
        else didMount.current = true;
    }, deps);
}

export default useDidMountEffect;

Usage example:

用法示例:

import React, { useState, useEffect } from 'react';

import useDidMountEffect from '../path/to/useDidMountEffect';

const MyComponent = (props) => {    
    const [state, setState] = useState({
        key: false
    });    

    useEffect(() => {
        // you know what is this, don't you?
    }, []);

    useDidMountEffect(() => {
        // react please run me if 'key' changes, but not on initial render
    }, [state.key]);    

    return (
        <div>
             ...
        </div>
    );
}
// ...

回答by ravi

@MehdiDehghani, your solution work perfectly fine, one addition you have to do is on unmount, reset the didMount.currentvalue to false. When to try to use this custom hook somewhere else, you don't get cache value.

@MehdiDehghani,您的解决方案工作得非常好,您必须做的一项补充是卸载,将didMount.current值重置为false. 什么时候尝试在其他地方使用这个自定义钩子,你不会得到缓存值。

import React, { useEffect, useRef } from 'react';

const useDidMountEffect = (func, deps) => {
    const didMount = useRef(false);

    useEffect(() => {
        let unmount;
        if (didMount.current) unmount = func();
        else didMount.current = true;

        return () => {
            didMount.current = false;
            unmount && unmount();
        }
    }, deps);
}

export default useDidMountEffect;

回答by Whatabrain

@ravi, yours doesn't call the passed-in unmount function. Here's a version that's a little more complete:

@ravi,你的没有调用传入的卸载函数。这是一个更完整的版本:

/**
 * Identical to React.useEffect, except that it never runs on mount. This is the equivalent of
 * the componentDidUpdate lifecycle function.
 *
 * @param {function:function} effect - A useEffect effect.
 * @param {array} dependencies - useEffect dependency list.
 */
export const useEffectExceptOnMount = (effect, dependencies) => {
  const mounted = React.useRef(false);
  React.useEffect(() => {
    if (mounted.current) {
      const unmount = effect();
      return () => unmount && unmount();
    } else {
      mounted.current = true;
    }
  }, dependencies);

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