【问题标题】:How to handle useEffect being called twice in strictMode for things that should only be run once?对于应该只运行一次的事情,如何处理在 strictMode 中被调用两次的 useEffect?
【发布时间】:2022-07-03 09:01:38
【问题描述】:

我最近遇到了一个问题,React useEffectstrictMode 中被调用了两次。我想保持严格模式以避免出现问题,但我没有看到任何确保某些特定效果只运行一次的好方法。这是在next.js 环境中,我特别希望代码只在客户端运行一次。

例如,给定以下组件:

import React, { useState, useEffect } from "react";
import ky from "ky";

const NeedsToRunOnce: React.FC = () => {
  const [loading, setLoading] = useState(false);

  const doSomethingThatOnlyShouldHappenOnce = (data: any) => {
    // Do something with the loaded data that should only happen once
    console.log(`This log appears twice!`, data);
  };

  useEffect(() => {
    if (loading) return;
    console.log("This happens twice despite trying to set loading to true");
    setLoading(true);
    const fetchData = async () => {
      const data = await ky.get("/data/json_data_2000.json").json();
      setLoading(false);
      doSomethingThatOnlyShouldHappenOnce(data);
    };
    fetchData();
  }, []);
  return <div></div>;
};

export default NeedsToRunOnce;

useEffect 将被调用两次,而两次loading 仍然为假。这是因为strictMode 使它以相同的状态被调用两次。该请求将通过两次,并调用doSomethingThatOnlyShouldHappenOnce 两次。上面的所有console.log 调用都会在控制台中出现两次。

由于我无法修改状态以让我的组件知道请求已经开始发生,我如何才能阻止 get 请求发生两次然后调用我想要的代码被调用一次? (就上下文而言,我正在使用useEffect 中加载的数据初始化一个外部库,并且该库应该只初始化一次)。

我找到了一个GitHub issue about this Dan Abramov 说:

通常您会希望对效果进行某种清理。它应该要么取消你的获取,要么通过在你的效果中设置一个变量来确保你忽略它的结果。完成此操作后,行为应该没有实际差异。

如果您取消 fetch in effect 清理,则只有一个请求将在开发中完成。但是,是否触发另一个请求也无关紧要。无论如何它都被忽略了,压力测试只发生在开发中。

虽然我原则上同意,但在实践中这很乏味。我已经在我的应用程序中遇到了两个不同的用例,其中我有一些只需要在客户端中完成一次的库调用或请求。

确保useEffect 中的一段代码仅在此处运行一次的可靠方法是什么?使用状态来跟踪它已经运行并没有帮助,因为react 连续两次以相同的状态调用该组件。

【问题讨论】:

  • 你的 effect 的依赖数组是空的,但是你的 effect 读取的是loading,所以它也必须依赖它。

标签: javascript reactjs typescript next.js


【解决方案1】:

您可以将此代码分成两部分,第一个组件获取数据,当数据已经存在时将其作为道具传递给第二个组件,并且您只在数据准备好时渲染第二个组件

【讨论】:

    【解决方案2】:

    你可以使用 ref 来执行你的 useEffect 一次(第一次或第二次,如你所愿),或者只使用 customHook。就我而言,我第二次执行 useEffect 。交换真假,第一次执行而不是第二次。

    import { useEffect, useRef } from "react";
    
    export default function useEffectOnce(fn: () => void) {
      const ref = useRef(false);
      useEffect(() => {
        if (ref.current) {
          fn();
        }
        return () => {
          ref.current = true;
        };
      }, [fn]);
    }
    

    要使用它,你可以在 param 中传递你的回调函数:

    useEffectOnce(() => console.log("hello"));
    

    【讨论】:

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