反应挂钩状态不会在递归中改变

React hook state not change in recursion

我有一个递归自定义挂钩,它触发 3 chances 的 setTimeout 函数,如果 chances 达到 0,它应该停止递归。

但是当代码运行时,setTimeout中的机会仍然是= 3,并且递归根本不会停止。

我认为这与关闭有关,但我无法弄清楚它是如何解决这个问题的。请给我解释一下。

例子 https://codesandbox.io/s/recursion-state-closures-30wuo?file=/src/App.js

import { useEffect, useState } from "react";

const DEFAULT_CHANCES = 3;

function useRecursion() {
  var [chances, setChances] = useState(DEFAULT_CHANCES);

  const resetChances = () => setChances(DEFAULT_CHANCES);

  const minusChancesRecursivly = () => {
    setTimeout(() => {
      setChances((prev) => --prev);

      if (chances > 0) {
        console.log(chances);
        minusChancesRecursivly();
      }
    }, 1000);
  };

  useEffect(() => {
    minusChancesRecursivly();
    return () => setChances(DEFAULT_CHANCES);
  }, []);

  return [chances, resetChances];
}

export default function App() {
  const [chances, resetChances] = useRecursion();

  return (
    <div className="App">
      <button onClick={resetChances}>Click</button>
      <h1>Chances: {chances}</h1>
    </div>
  );
}

您的 minusChancesRecursivly 中存在问题,您在 settimeout 中访问 chances。每次调用该函数时,chances 的值将始终保持不变,就像您调用它时的值一样。您可以通过将代码放在您传递给 setChances 的回调中来解决此问题,它在您的 prev 参数中接收机会的当前值。

但是更好的方法是将当前的机会值作为参数传递给函数,就像这样

  const minusChancesRecursivly = (chancesLeft) => {
    setChances(chancesLeft);

    if (chancesLeft > 0) {
      setTimeout(
        () => minusChancesRecursivly(chancesLeft - 1),
        1000
      );
    }
  };

然后调用如下

minusChancesRecursivly(3);

在我看来,这会产生更清晰的代码,这些代码是独立于技术以相同的方式编写的。我的意思是 - 相同的概念在其他框架或语言中也同样适用。

修改后的代码在这里 https://codesandbox.io/s/recursion-state-closures-forked-jbfgo?file=/src/App.js

(我还添加了对竞争条件的预防,因此您可以在单击重置按钮时停止之前的超时。)

您的代码有一个问题。

在您设置新值之前,您检查过它

  setChances((prev) => --prev);

  if (chances > 0) {
    console.log(chances);
    minusChancesRecursivly();
  }

这一定行得通

import { useEffect, useState } from "react";

const DEFAULT_CHANCES = 3;

function useRecursion() {
  var [chances, setChances] = useState(DEFAULT_CHANCES);

  const resetChances = () => setChances(DEFAULT_CHANCES);

  const minusChancesRecursivly = () => {
    setTimeout(() => {
      setChances((prev) => --prev);
    }, 1000);
  };

  useEffect(() => {
    if (chances > 0) {
      minusChancesRecursivly();
    }
  }, [chances]);

  return [chances, resetChances];
}

export default function App() {
  const [chances, resetChances] = useRecursion();

  return (
    <div className="App">
      <button onClick={resetChances}>Click</button>
      <h1>Chances: {chances}</h1>
    </div>
  );
}