【问题标题】:custom hook -> useEffect -> while loop not breaking自定义钩子 -> useEffect -> while 循环不中断
【发布时间】:2021-08-27 17:11:20
【问题描述】:

我尝试为轮询创建一个自定义挂钩。

问题:

我的钩子在 useEffect 中使用了一个 while 循环,但似乎 while 循环永远不会中断,即使我将条件更改为 false。

代码沙箱重现问题:

https://codesandbox.io/s/clever-worker-jm1p5?file=/src/App.tsx

代码:

我有 2 个文件:

  1. usePolling.ts(这是我的钩子)
  2. App.tsx(我正在执行/调用钩子的地方)
usePolling.ts
/* eslint-disable no-await-in-loop */
import { useState, useCallback, useEffect } from "react";

export interface PollingOptions<T> {
  fetchFunc: () => Promise<T> | undefined;
}

export const usePolling = <T>(
  { fetchFunc }: PollingOptions<T>,
  interval: number
) => {
  const [data, setData] = useState<T>();
  const [error, setError] = useState<Error>();
  const [condition, setCondition] = useState(true);

  const stopPolling = useCallback(() => {
    setCondition(false);
  }, []);

  const performPolling = useCallback(async () => {
    try {
      const res = await fetchFunc();
      setData(res);
    } catch (err) {
        setCondition(false);
        setError(err);
        return;
    } finally {
      await new Promise((r) => setTimeout(r, interval));
    }
  }, [fetchFunc, interval]);

  useEffect(() => {
    (async () => {
      while (condition) {
        await performPolling();
      }
    })();

    return () => stopPolling();
  }, [condition, performPolling, stopPolling, data]);

  return { data, error, stopPolling };
};
App.tsx
import { useCallback, useMemo } from "react";
import { usePolling } from "./usePolling";
import "./styles.css";

export default function App() {
  const fetchFunc = useCallback(async () => {
    // please consider this as an API call
    await new Promise((resolve) => setTimeout(resolve, 500));
    return { isSigned: Math.random() < 0.5 };
  }, []);

  const { data, error, stopPolling } = usePolling({ fetchFunc }, 3000);

  console.log(data, error, ">>>>>>>");

  const isSigned = data?.isSigned;

  const isContractSigned = useMemo(() => {
    if (isSigned) stopPolling();
    return isSigned || false;
  }, [isSigned, stopPolling]);

  return (
    <div className="App">
      <h1>{`Is contract signed: ${isContractSigned}`}</h1>
    </div>
  );
}

为什么我不使用 setInterval 而不是 while 循环

例如,如果我使用 setInterval,并且选择将轮询间隔更改为 100 毫秒。有3个问题:

  1. 很有可能在之前的 API 调用响应之前调用 API。
  2. 假设出于任何原因,第二个 API 调用在第一个调用之前响应,那么我就有麻烦了
  3. 不必要的 API 调用。

如果我使用while循环而不是setInterval,那么上面提到的所有问题都解决了。请注意,我仍然在 while 循环中使用 setTimeout,所以它会在下一次迭代之前等待指定的时间间隔。

预期的解决方案:

任何不使用 setInterval 的解决方案都是预期的。如果您建议对现有代码进行任何改进(甚至与此特定问题无关),欢迎您:)

谢谢!

【问题讨论】:

  • useEffect 中的代码将在每次依赖项更改时运行。所以当condition发生变化时,useEffect里面的代码会再次运行,如果false不会执行while循环,但是useEffect第一次运行的while循环仍然会无限执行

标签: reactjs typescript react-typescript


【解决方案1】:

作为 useEffect 的第一个参数传递的回调假定依赖项是常量。

您正在启动一个条件为常量的 while 循环。没有办法停止 while 循环的执行。 如果更改条件,它不会停止前一个 while 循环,而是使用新值启动一个新条件。

如果新值为 false,它将不会启动,因此不会发生任何事情。但如果它是真的,那么你有 2 个 while 循环正在运行。

查看SWR, React Hooks for Data Fetching,它可能正是您想要的。

更新:

while循环不停止的原因

当您在普通代码中有一个 while 循环时,您可以使用基于 变量 的条件来控制它。 useEffect 的依赖项作为 constants 传递给回调。 因此,任何基于 useEffect 中的状态变量(真实)运行的循环都不会终止。

可以清理一个setInterval,因为在浏览器中实现了clearInterval函数,它接受一个id,找到interval的引用,然后停止它。 没有clearWhileLoop,因为它不可行。 每当依赖项发生变化时,清理函数就会运行,但您不能对清理函数中的 while 循环做任何事情。 使用 新常量 触发下一个回调。

查看why useEffects run after each render 的文档

【讨论】:

  • 感谢您回答我的问题。我不希望在项目中包含新的依赖项,例如 swr。我过去已经使用过 swr 和 react-query,但我不想将它包含在这个项目中。如果你看到我的实现,我会在 useEffect 中返回 stopPolling。这意味着,如果另一次调用 effect,它将确保在开始新的循环之前中断正在运行的 while 循环。我不确定为什么这里不是这样。您能否尝试检查我的代码,您能否建议我对当前实现本身进行更改(如果可能)谢谢!
  • 没有问题 Vishal,这里的要点是,当您在 useEffect 中处理状态变量时,它们将被视为常量。在 useEffect 中基于状态变量 (truthy) 的真实条件下运行的任何循环都不会终止。我也更新了答案。
  • 这是更新后的钩子,它仍然使用 while 循环,现在可以正常工作:codesandbox.io/s/clever-worker-jm1p5?file=/src/usePolling.ts
  • 不错的维沙尔!你切换到裁判了,GG!
猜你喜欢
  • 2021-11-14
  • 1970-01-01
  • 1970-01-01
  • 2021-09-10
  • 1970-01-01
  • 2020-02-19
  • 2022-10-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多