代码之家  ›  专栏  ›  技术社区  ›  Yangshun Tay

使React useEffect挂钩在初始渲染时不运行

  •  5
  • Yangshun Tay  · 技术社区  · 6 年前

    根据文件:

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

    我们可以用新的 useEffect() componentDidUpdate() ,但似乎

    正如您在下面的示例中所看到的, 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/react@16.7.0-alpha.0/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
    
    <div id="app"></div>
    1 回复  |  直到 6 年前
        1
  •  165
  •   Tholle    6 年前

    We can use the useRef hook to store any mutable value we like ,所以我们可以用它来跟踪 useEffect 函数正在运行。

    如果我们想让效果在相同的阶段运行 componentDidUpdate useLayoutEffect

    例子

    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/react@16.7.0-alpha.0/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
    
    <div id="app"></div>
        2
  •  93
  •   Mehdi Dehghani    5 年前

    你可以把它变成 custom 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;
    

    用法示例:

    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>
        );
    }
    // ...
    
        3
  •  26
  •   Marius Marais    4 年前

    我做了一个简单的决定 useFirstRender 钩住以处理诸如聚焦表单输入之类的情况:

    import { useRef, useEffect } from 'react';
    
    export function useFirstRender() {
      const firstRender = useRef(true);
    
      useEffect(() => {
        firstRender.current = false;
      }, []);
    
      return firstRender.current;
    }
    

    一开始是 true ,然后切换到 false useEffect ,它只运行一次,不会再运行。

    const firstRender = useFirstRender();
    const phoneNumberRef = useRef(null);
    
    useEffect(() => {
      if (firstRender || errors.phoneNumber) {
        phoneNumberRef.current.focus();
      }
    }, [firstRender, errors.phoneNumber]);
    

    对于您的情况,您只需使用 if (!firstRender) { ... .

        4
  •  5
  •   Whatabrain ravi    5 年前

    @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);
    
      // Reset on unmount for the next mount.
      React.useEffect(() => {
        return () => mounted.current = false;
      }, []);
    };
        5
  •  0
  •   Whatabrain ravi    5 年前

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