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

时间:2021-06-08 09:26:01

标签: javascript reactjs react-hooks closures

我有一个递归自定义钩子,它为 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>
  );
}

2 个答案:

答案 0 :(得分:0)

您的 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

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

答案 1 :(得分:0)

您的代码有一个问题。

在你设置新值之前你检查过它

  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>
  );
}