如何防止useEffect在React中的挂载运行?

182 阅读2分钟

有时我们不希望一个useEffect 钩子在初始渲染时运行。这可能有很多原因,但一个常见的用例是当我们正在获取该钩子所依赖的数据时。最初,数据是空的,所以运行该钩子是没有意义的。

一个理论上的例子

在下面这个组件中,我们fetch 一些数据,然后我们打算在取完数据后用这些数据doSomething

function MyComponent() {
  const [data, setData] = useState();

  // An effect to fetch the data
  useEffect(() => {
    fetch('/api/some-api')
      .then((res) => res.json())
      .then((d) => {
        setData(d);
      });
  }, []);

  // Do something else with the data
  useEffect(() => {
    doSomething(data);
  }, [data]);
}

在初始渲染时,我们fetch 数据,但我们运行我们的第二个效果。

重要的是: useEffect 钩子将总是在加载时运行,不管它的依赖数组中是否有任何东西。

我们可能不想在我们的data ,当它是undefined (因为它将在初始渲染时)时实际运行这个效果,而是想等到它从API调用中被填充。

useRef来拯救!

解决这个问题的一个办法是采用useRef 钩子来跟踪组件是否已经安装。虽然ref 通常被认为是获得对DOM元素引用的一种方式,但它也可以用来引用其他对象和基元。

在这种情况下,我们将创建一个ref ,指向一个布尔值,以跟踪组件是否已经安装。它一开始会是false ,但一旦effect 第一次运行,我们就可以把它改为true

这可能是更容易理解的代码。

function MyComponent() {
  const [data, setData] = useState();
  const isMounted = useRef(false);

  // An effect to fetch the data
  useEffect(() => {
    fetch('/api/some-api')
      .then((res) => res.json())
      .then((d) => {
        setData(d);
      });
  }, []);

  // Do something else with the data
  useEffect(() => {
    if (isMounted.current) {
      doSomething(data);
    } else {
      isMounted.current = true;
    }
  }, [data]);
}

isMounted.current 开始时是 ,所以我们的第二个 钩子的第一次运行不会调用 。相反,它将把 设置为 。在钩子的后续运行中, 将成为 , 将在我们的数据上执行。false useEffect doSomething isMounted.current true isMounted.current true doSomething

为什么采用这种方法?

为这个目的使用ref ,有几个好处。

  • 我们可以确信,在钩子的任何后续运行中,我们的data 将被改变--这是依赖数组中的唯一值。
  • 通过不使用状态来跟踪isMounted ,我们就不会强迫进行额外的组件渲染。isMounted.current = true 正在改变我们的ref (这是它的预期用途),因此它是同一个对象/不会导致重新渲染。

总结

useEffect 钩子可能特别具有挑战性。通过了解useRef 钩子,我们可以对我们的效果运行时间获得更多的控制。