如何处理 useEffect 在 strictMode 中被调用两次而只应运行一次的事情?

Mal*_*hak 5 javascript typescript reactjs next.js

我知道 React 在严格模式下调用 useEffect 两次,这个问题是关于询问正确的处理方法是什么。

\n

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

\n

例如,给定以下组件:

\n
import React, { useState, useEffect } from "react";\nimport ky from "ky";\n\nconst NeedsToRunOnce: React.FC = () => {\n  const [loading, setLoading] = useState(false);\n\n  const doSomethingThatOnlyShouldHappenOnce = (data: any) => {\n    // Do something with the loaded data that should only happen once\n    console.log(`This log appears twice!`, data);\n  };\n\n  useEffect(() => {\n    if (loading) return;\n    console.log("This happens twice despite trying to set loading to true");\n    setLoading(true);\n    const fetchData = async () => {\n      const data = await ky.get("/data/json_data_2000.json").json();\n      setLoading(false);\n      doSomethingThatOnlyShouldHappenOnce(data);\n    };\n    fetchData();\n  }, []);\n  return <div></div>;\n};\n\nexport default NeedsToRunOnce;\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

我发现了一个关于此问题的 GitHub 问题,其中 Dan Abramov 说道:

\n
\n

通常,您\xe2\x80\x99d 希望对您的效果进行某种清理。它应该取消您的获取,或者通过在效果中设置变量来确保您忽略其结果。一旦执行此操作,行为上就不会有实际差异。

\n
\n
\n

如果您取消有效清理中的提取,则开发中只会完成一个请求。然而,如果另一个请求触发,\xe2\x80\x99 也没关系。它\xe2\x80\x99无论如何都会被忽略,并且压力测试仅发生在开发中。

\n
\n

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

\n

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

\n

小智 3

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

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

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

useEffectOnce(() => console.log("hello"));
Run Code Online (Sandbox Code Playgroud)