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

问题描述 投票:0回答:1

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

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

应对沙盒: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 closures
1个回答
0
投票

任何时候next状态值取决于previous状态值,例如就像一个值与前一个值的字符串连接,你会想要使用一个功能状态更新

更新

runLoop
处理程序以使用功能状态更新,这确实是一个相当微不足道的更改:
setOutput(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(例如渲染的内容)是状态和道具的函数。 React 状态应该包含 just 您需要存储的数据,React 组件将状态映射到呈现的 UI。

这里是存储just

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

© www.soinside.com 2019 - 2024. All rights reserved.