【问题标题】:How to call loading function with React useEffect only once如何仅使用 React useEffect 调用加载函数一次
【发布时间】:2019-04-06 20:09:25
【问题描述】:

useEffect React 挂钩将在每次更改时运行传入的函数。这可以优化为仅在所需属性更改时才调用。

如果我想从componentDidMount 调用一个初始化函数而不是在更改时再次调用它怎么办?假设我想加载一个实体,但加载函数不需要来自组件的任何数据。我们如何使用useEffect 钩子来做到这一点?

class MyComponent extends React.PureComponent {
    componentDidMount() {
        loadDataOnlyOnce();
    }
    render() { ... }
}

使用钩子,它可能看起来像这样:

function MyComponent() {
    useEffect(() => {
        loadDataOnlyOnce(); // this will fire on every change :(
    }, [...???]);
    return (...);
}

【问题讨论】:

    标签: javascript reactjs react-hooks


    【解决方案1】:

    如果你只想在初始渲染后运行给useEffect的函数,你可以给它一个空数组作为第二个参数。

    function MyComponent() {
      useEffect(() => {
        loadDataOnlyOnce();
      }, []);
    
      return <div> {/* ... */} </div>;
    }
    

    【讨论】:

    • 或者,如果有用于获取数据的参数(例如用户 ID),您可以在该数组中传递用户 ID,如果它发生更改,组件将重新获取数据。许多用例都可以这样工作。
    • 是的...有关跳过的更多信息记录在这里:reactjs.org/docs/…
    • 这似乎是最简单的答案,但 ESLint 抱怨...查看此线程上的其他答案 stackoverflow.com/a/56767883/1550587
    • 只需将 loadDataOnlyOnce 传递给依赖项数组。这行得通吗?
    • 不,因为当 loadDataOnlyOnce 改变时(在这个例子中没有,但 lint 无论如何都不会抱怨非局部变量),它会重新运行效果。解决方案是为钩子创建一个单独的函数,就像在此处的另一个答案中一样(有效地欺骗 ESLint),或者在第一次运行后设置一个带有布尔值的 useRef,如果设置了就不要再次运行。
    【解决方案2】:

    将一个空数组作为第二个参数传递给useEffect。这有效地告诉 React,引用 docs:

    这告诉 React 你的效果不依赖于任何来自 props 或 state 的值,所以它永远不需要重新运行。

    这是一个 sn-p,您可以运行它来证明它有效:

    function App() {
      const [user, setUser] = React.useState(null);
    
      React.useEffect(() => {
        fetch('https://randomuser.me/api/')
          .then(results => results.json())
          .then(data => {
            setUser(data.results[0]);
          });
      }, []); // Pass empty array to only run once on mount.
      
      return <div>
        {user ? user.name.first : 'Loading...'}
      </div>;
    }
    
    ReactDOM.render(<App/>, 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>

    【讨论】:

      【解决方案3】:

      TL;DR

      useEffect(yourCallback, []) - 只会在第一次渲染后触发回调。

      详细解释

      useEffect 默认在组件的每次渲染之后运行(从而产生效果)。

      在你的组件中放置 useEffect 时,你告诉 React 你想要运行回调作为一个效果。 React 将在渲染后和执行 DOM 更新后运行效果。

      如果您只传递一个回调 - 回调将在每次渲染后运行。

      如果传递第二个参数(数组),React 将在第一次渲染之后以及每次更改数组中的一个元素时运行回调。例如,在放置 useEffect(() =&gt; console.log('hello'), [someVar, someOtherVar]) 时 - 回调将在第一次渲染之后以及在 someVarsomeOtherVar 之一发生更改的任何渲染之后运行。

      通过向第二个参数传递一个空数组,React 将在每次渲染后比较该数组并发现没有任何变化,因此仅在第一次渲染后调用回调。

      【讨论】:

        【解决方案4】:

        使用MountEffect挂钩

        在组件挂载后只运行一次函数是一种常见的模式,它证明了它自己的一个隐藏实现细节的钩子是合理的。

        const useMountEffect = (fun) => useEffect(fun, [])
        

        在任何功能组件中使用它。

        function MyComponent() {
            useMountEffect(function) // function will run only once after it has mounted. 
            return <div>...</div>;
        }
        

        关于 useMountEffect 挂钩

        当使用useEffect 和第二个数组参数时,React 将在挂载(初始渲染)和数组中的值发生更改后运行回调。由于我们传递的是一个空数组,所以它只有在挂载后才会运行。

        【讨论】:

        • 我非常喜欢你的答案,因为 ESLint 规则“re​​act-hooks/exhaustive-deps”在空依赖列表上总是会失败。例如,著名的 create-react-app 模板将强制执行该规则。
        • 现在你可以使用 useMount 当你的效果函数需要来自 props 的东西,但即使该值在没有 linter 警告的情况下更改,也永远不需要再次运行:useEffect(()=&gt;console.log(props.val),[]) 将缺少依赖警告,但 useMount(()=&gt;console.log(props.val))不会引起警告,但“确实有效”。我不确定并发模式是否会出现问题。
        • 我不太明白..."react-hooks/exhaustive-deps" 仍然抱怨const useMountEffect = (fun) =&gt; useEffect(fun, []) 中的空数组
        • 谢谢!虽然我认为这指出了"react-hooks/exhaustive-deps" 中的一个缺陷,特别是因为这是在挂载上运行事物的规范方式。这个“解决方案”在功能上将问题从组件转移到其他地方,而不是从根本上解决空部门的问题。
        • 这不会“绕过” ESLint 规则 b/c 它仍然会指出 useEffect 具有依赖关系:fun
        【解决方案5】:

        我喜欢定义一个 mount 函数,它以与 useMount 相同的方式欺骗​​ EsLint,我觉得它更不言自明。

        const mount = () => {
          console.log('mounted')
          // ...
        
          const unmount = () => {
            console.log('unmounted')
            // ...
          }
          return unmount
        }
        useEffect(mount, [])
        
        

        【讨论】:

          【解决方案6】:

          将依赖数组留空。希望这能帮助您更好地理解。

             useEffect(() => {
                doSomething()
              }, []) 
          

          空依赖数组只在挂载上运行一次

          useEffect(() => {
            doSomething(value)
          }, [value])  
          

          value 作为依赖项传递。如果依赖关系自上次以来发生了变化,效果将再次运行。

          useEffect(() => {
            doSomething(value)
          })  
          

          没有依赖。每次渲染后都会调用它。

          【讨论】:

            【解决方案7】:
            function useOnceCall(cb, condition = true) {
              const isCalledRef = React.useRef(false);
            
              React.useEffect(() => {
                if (condition && !isCalledRef.current) {
                  isCalledRef.current = true;
                  cb();
                }
              }, [cb, condition]);
            }
            

            并使用它。

            useOnceCall(() => {
              console.log('called');
            })
            

            useOnceCall(()=>{
              console.log('Fetched Data');
            }, isFetched);
            

            【讨论】:

            • 谢谢!拯救了我的一天。非常适合调用函数一次,但仅在需要加载某些状态之后。
            【解决方案8】:

            这是我对 Yasin 的回答。

            import {useEffect, useRef} from 'react';
            
            const useOnceEffect = (effect: () => void) => {
              const initialRef = useRef(true);
            
              useEffect(() => {
                if (!initialRef.current) {
                  return;
                }
                initialRef.current = false;
                effect();
              }, [effect]);
            };
            
            export default useOnceEffect;
            
            

            用法:

            useOnceEffect(
              useCallback(() => {
                nonHookFunc(deps1, deps2);
              }, [deps1, deps2])
            );
            

            【讨论】:

              【解决方案9】:

              我们必须停止思考组件生命周期方法(即componentDidMount)。我们必须开始思考效果。 React 效果不同于旧式的类生命周期方法。

              默认情况下,效果会在每个渲染周期后运行,但可以选择退出此行为。要选择退出,您可以定义依赖项,这意味着仅在对其中一个依赖项进行更改时才会执行效果。

              如果您明确定义效果没有依赖关系,则该效果仅在第一个渲染周期之后运行一次。

              第一种解决方案(使用 ESLint 投诉)

              因此,您的示例的第一个解决方案如下:

              function MyComponent() {
              
                  const loadDataOnlyOnce = () => {
                    console.log("loadDataOnlyOnce");
                  };
              
                  useEffect(() => {
                      loadDataOnlyOnce(); // this will fire only on first render
                  }, []);
                  return (...);
              }
              

              但是 React Hooks ESLint 插件会抱怨这样的事情:

              React Hook useEffect has missing dependency: loadDataOnlyOnce. Either include it or remove the dependency array.

              起初这个警告看起来很烦人,但请不要忽视它。它可以帮助您更好地编码。

              第二个解决方案(正确的方法,如果依赖不依赖于组件)

              如果我们将loadDataOnlyOnce 添加到依赖数组中,我们的效果将在每个渲染周期后运行,因为loadDataOnlyOnce 的引用在每次渲染时都会发生变化,因为函数被销毁(垃圾收集)和一个新函数已创建,但这正是我们不想要的。

              我们必须在渲染周期中保持loadDataOnlyOnce 的相同引用。

              所以只需移动上面的函数定义:

              const loadDataOnlyOnce = () => {
                console.log("loadDataOnlyOnce");
              };
              
              function MyComponent() {
                  useEffect(() => {
                      loadDataOnlyOnce(); // this will fire only on first render
                  }, []);
                  return (...);
              }
              

              第三种解决方案(正确的方法,如果依赖依赖于组件)

              如果效果的依赖(loadDataOnlyOnce),依赖于组件(需要道具或状态),有React的内置useCallback-Hook。

              useCallback-Hook 的基本意义是在渲染周期内保持函数的引用相同。

              function MyComponent() {
                  const [state, setState] = useState("state");
              
                  const loadDataOnlyOnce = useCallback(() => {
                    console.log(`I need ${state}!!`);
                  }, [state]);
              
                  useEffect(() => {
                      loadDataOnlyOnce(); // // this will fire only when loadDataOnlyOnce-reference changes
                  }, [loadDataOnlyOnce]);
                  return (...);
              }
              

              【讨论】:

              • 这个答案值得初学者 React 开发者更多关注。
              • @JusticeBringer 我想说的不仅仅是初学者。这不是一个简单的概念。
              • 第四个选项。将函数内容直接放入useEffect。这也消除了“eslint”错误。感谢您顺便解释一下这个概念。
              【解决方案10】:

              如果您只是在渲染后调用 useeffect 中的函数,则添加一个空数组作为 useeffect 的第二个参数

              useEffect=(()=>{
                 functionName(firstName,lastName);
              },[firstName,lastName])
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2020-01-05
                • 1970-01-01
                • 2021-05-16
                • 2022-06-12
                • 2020-08-17
                • 2019-11-12
                • 1970-01-01
                相关资源
                最近更新 更多