如何在从循环函数调用的函数中使用新的 useState 值,包括 React 中的超时?

How can I use a fresh useState value in a function called from a looped function including a timeout in React?

提问人:Korimako 提问时间:5/11/2023 最后编辑:Korimako 更新时间:5/11/2023 访问量:52

问:

在此代码中,我希望输出打印“Output:,1,2,3,4,5” 但是输出变量是过时的,因为我在从闭包返回 promise 后迭代调用该函数。

有什么有效的方法可以保持这种定时睡眠效果的循环,并获得我想要的结果?

代码沙盒:https://codesandbox.io/s/hidden-shape-6uh5jm?file=/src/App.js:0-1092

import "./styles.css";
import React, { useEffect, useState } from "react";

export default function App() {
  const [output, setOutput] = useState("Output: ");
  const [isLooping, setIsLooping] = useState(false);

  const sleep = (ms) => {
    return function (x) {
      return new Promise((resolve) => setTimeout(() => resolve(x), ms));
    };
  };

  const startLoop = () => {
    if (isLooping) {
      console.log("Click ignored - because is Looping");
      return;
    } else {
      setIsLooping(true);
      runLoop();
    }
  };

  const runLoop = (maxIterations = 0) => {
    setOutput(output + ", " + maxIterations);
    if (maxIterations >= 5) {
      setIsLooping(false);
      return;
    }
    sleep(1000)().then(() => {
      runLoop(maxIterations + 1);
    });
  };

  useEffect(() => {
    startLoop();
  }, []);

  return (
    <div className="App">
      <p>{output}</p>
      <button
        value="Start Loop"
        onClick={() => {
          startLoop();
        }}
      >
        Start loop
      </button>
      {isLooping && <p>Is Looping</p>}
    </div>
  );
}
JavaScript ReactJS React-Hooks 闭包

评论

0赞 Jacob Smit 5/11/2023
setOutput(output + ", " + maxIterations);自?setOutput(currentValue => currentValue + ", " + maxIterations);

答:

0赞 Drew Reese 5/11/2023 #1

每当下一个状态值依赖于前一个状态值时,例如,将一个值连接到前一个值,您将需要使用功能状态更新

更新处理程序以使用功能状态更新,这实际上是一个相当微不足道的更改:到 .runLoopsetOutput(output + ", " + maxIterations);setOutput(output => output + ", " + maxIterations);

const runLoop = (maxIterations = 0) => {
  setOutput(output => output + ", " + maxIterations);
  if (maxIterations >= 5) {
    setIsLooping(false);
    return;
  }
  sleep(1000)().then(() => {
    runLoop(maxIterations + 1);
  });
};

不过,这会导致一个奇怪的输出:"Output: , 0, 1, 2, 3, 4, 5"

这基本上是一个典型的篱笆问题

这是将要呈现的 UI 与要从中呈现的数据混合在一起的结果,全部进入状态。请记住,在 React 中,UI(例如渲染的内容)是状态和 props 的函数。React 状态应该包含你需要存储的数据,而 React 组件将状态映射到渲染的 UI。

下面是一个存储值并呈现该状态的计算输出字符串的示例。maxIterations

import "./styles.css";
import React, { useEffect, useState } from "react";

export default function App() {
  const [output, setOutput] = useState([]); // <-- initial empty array
  const [isLooping, setIsLooping] = useState(false);

  const sleep = (ms) => {
    return function (x) {
      return new Promise((resolve) => setTimeout(() => resolve(x), ms));
    };
  };

  const startLoop = () => {
    if (isLooping) {
      console.log("Click ignored - because is Looping");
      return;
    } else {
      // reset array for next looping
      setOutput([]);
      setIsLooping(true);
      runLoop();
    }
  };

  const runLoop = (maxIterations = 0) => {
    setOutput((output) => output.concat(maxIterations)); // <-- append new value, return new array
    if (maxIterations >= 5) {
      setIsLooping(false);
      return;
    }
    sleep(1000)().then(() => {
      runLoop(maxIterations + 1);
    });
  };

  useEffect(() => {
    startLoop();
  }, []);

  return (
    <div className="App">
      <p>Output: {output.join(", ")}</p> // <-- compute rendered UI
      <button
        value="Start Loop"
        onClick={() => {
          startLoop();
        }}
      >
        Start loop
      </button>
      {isLooping && <p>Is Looping</p>}
    </div>
  );
}

渲染的输出现在将更令人期待"Output: 0, 1, 2, 3, 4, 5"

演示

Edit how-can-i-use-a-fresh-usestate-value-in-a-function-called-from-a-looped-function