React useState(),等待多个setState()调用的正确方法是什么?

huangapple go评论68阅读模式
英文:

React useState(), what is the correct way to wait for multiple setState() calls?

问题

在一个功能性的React组件内部,您可能需要更新多个状态变量。如何确保在运行下一个函数之前所有状态更改都已完成呢?

例如,假设在某个事件上触发calculateScore()(也许是游戏中的某些点击),它会增加a、b或两者的值。然后,您希望运行determineWinner()。如何以一种方式运行determineWinner(),以确保setAsetB都已完成它们的异步操作,以便a和b都是最新的(在单个calculateScore()运行后)?

我没有在文档中找到这个信息,也没有在StackOverflow上搜索一段时间后找到它。

示例:

function MyComponent() {
    let [a, setA] = useState(0);
    let [b, setB] = useState(0);
    let [winnerText, setWinnerText] = useState("")

    function determineWinner() {
        if (a > b) {
            // 请注意,如果得分在calculateScore()的setA和setB完成后相等,我不希望这种情况发生。
            setWinnerText("A赢了!")
        }
    }

    function calculateScore(aShouldIncrease, bShouldIncrease) {
        if (aShouldIncrease) setA(prevA => prevA + 1);
        if (bShouldIncrease) setB(prevB => prevB + 1);

        determineWinner(); // 在这里运行它是不行的,因为setA和setB是异步的。
    }

    useEffect(() => {
        determineWinner(); // 不能保证在这里工作,因为也许只有setA已经完成,而setB尚未完成?对吗?
    }, [a, b])

    determineWinner(); // 即使我们假设我有某种条件来防止它过早运行,它也可能在只有a或只有b已更改时运行,对吗?

    return <h1>{ winnerText }</h1>
}

我意识到我可以通过在calculateScore 中进行所有计算而不设置状态来解决这个问题,但我想知道是否有一种在React中可以确保从单个函数调用中启动的所有setState()函数都已完成的时机。

英文:

In a function inside a functional react component I may update multiple state variables. How can I ensure all state changes have taken place before running the next function.

Below, for example, imagine that calculateScore() is triggered on some event (perhaps some clicks in a game) and adds to the value of either a, b or both. I then wan't to run determineWinner(). How can I run determineWinner() in a way which ensures both setA and setB have completed their asynchronous runs so that both a and b are up-to-date (after a single calculateScore() run)?

I didn't find this in the docs or while searching around for a while here on stackOverflow.

Example

function MyComponent() {
    let [a, setA] = useState(0);
    let [b, setB] = useState(0);
    let [winnerText, setWinnerText] = useState(&quot;&quot;)

    function determineWinner() {
        if (a &gt; b) {
            // Note, I don&#39;t want this to happen if the score ends up equal after both setA and setB from calculateScore() completes.
            setWinnerText(&quot;A won!&quot;)
        }
    }

    function calculateScore(aShouldIncrease, bShouldIncrease) {
        if (aShouldIncrease) setA(prevA =&gt; prevA + 1);
        if (bShouldIncrease) setB(prevB =&gt; prevB + 1);

        determineWinner(); // Doesn&#39;t work to run it here, since setA and setB are asynchronous.
    }

    useEffect(() =&gt; {
        determineWinner(); // Not guaranteed to work here, since perhaps only setA has completed so far and not setB? Right?
    }, [a, b])

    determineWinner(); // Even if we imagined I had some other condition to prevent it from being run prematurely, it may run when only a or only b has changed, right?

    return &lt;h1&gt;{ winnerText }&lt;/h1&gt;
}

I realize I could solve it by making all the calculations within the calculateScore without setting state, but I wonder if there is some point in react where I can be sure all setState() functions started from within a single function call will be completed.

答案1

得分: 2

从语义上看,似乎您想在“游戏得分”更新时执行一个操作,而不是在其中的任何单个值更新时执行操作。因此,当前的代码中没有考虑封装级别。

将“游戏状态”保留在一个状态对象中,而不是两个。例如,考虑以下方式:

const [scores, setScores] = useState({a: 0, b: 0});

然后,更新该状态将成为单一的“set”操作:

function calculateScore(aShouldIncrease, bShouldIncrease) {
    const newScores = { ...scores };
    if (aShouldIncrease) {
        newScores.a++;
    }
    if (bShouldIncrease) {
        newScores.b++;
    }
    if (aShouldIncrease || bShouldIncrease) {
        setScores(newScores);
    }
}

然后,由于只更新了一个状态对象,您可以仅触发具有该依赖关系的效果:

useEffect(() => {
    determineWinner();
}, [scores]);
英文:

Semantically it sounds like you want to perform an action when "the game score" is updated, not when any individual value therein is updated. So there's a level of encapsulation not accounted for in the current code.

Keep the "game state" in one state object rather than two. For example, consider this:

const [scores, setScores] = useState({a: 0, b: 0});

Then updating that state would be a single "set" operation:

function calculateScore(aShouldIncrease, bShouldIncrease) {
    const newScores = { ...scores };
    if (aShouldIncrease) {
        newScores.a++;
    }
    if (bShouldIncrease) {
        newScores.b++;
    }
    if (aShouldIncrease || bShouldIncrease) {
        setScores(newScores);
    }
}

Then since only one state object was updated, you can trigger the effect with just that dependency:

useEffect(() =&gt; {
    determineWinner();
}, [scores]);

答案2

得分: 0

function calculateScore(aShouldIncrease, bShouldIncrease) {
   unstable_batchedUpdates(() => {
     if (aShouldIncrease) setA(prevA => prevA + 1);
     if (bShouldIncrease) setB(prevB => prevB + 1);

      determineWinner();
   });
}
英文:
function calculateScore(aShouldIncrease, bShouldIncrease) {
   unstable_batchedUpdates(() =&gt; {
     if (aShouldIncrease) setA(prevA =&gt; prevA + 1);
     if (bShouldIncrease) setB(prevB =&gt; prevB + 1);

      determineWinner();
   });
}

huangapple
  • 本文由 发表于 2023年2月16日 02:36:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/75464112.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定