为什么useEffect呈现意外值?

时间:2020-04-07 23:03:43

标签: javascript reactjs use-effect

我正在尝试为测验应用程序创建记分板。回答问题后,索引将更新。这是组件的代码。

export const ScoreBoard = ({ result, index }) => {
    const [score, setScore] = useState(0)
    const [total, setTotal] = useState(0)
    const [rightAns, setRight] = useState(0)

    useEffect(() => {
        if(result === true ) { 
            setRight(rightAns + 1)
            setTotal(total + 1)

        }
        if(result === false) {
            setTotal(total + 1)
        }
        setScore(right/total)
    }, [index]);

    return (
        <>
        <div>{score}</div>
        <div>{rightAns}</div>
        <div>{total}</div>
        </>
    )

    }

首次渲染时,值为

score = NaN
rightAns = 0
total = 0

单击正确答案之一后,值将更新为

score = NaN
rightAns = 1 
total = 1

,然后在又一个答案(具有错误值)之后,它更新为

score = 1
rightAns = 1
total = 2

分数不再是NaN,但仍显示不正确的值。在这三个渲染之后,应用程序开始将分数更新为滞后值。

score = 0.5
rightAns = 2
total = 3

前3个渲染期间发生了什么,我该如何解决?

4 个答案:

答案 0 :(得分:3)

您根本不应该将分数存储在状态中,因为它可以根据其他状态进行计算。

所有状态更改调用都是异步的,状态值只有在重新呈现后才更改,这意味着您仍在访问旧值。

export const ScoreBoard = ({ result, index }) => {
    const [total, setTotal] = useState(0)
    const [rightAns, setRight] = useState(0)

    useEffect(() => {
        if(result === true ) { 
            setRight(rightAns + 1)
            setTotal(total + 1)

        }
        if(result === false) {
            setTotal(total + 1)
        }
    }, [index]);

    const score = right/total
    return (
        <>
        <div>{score}</div>
        <div>{rightAns}</div>
        <div>{total}</div>
        </>
    )
}

更简单,并遵循关于single "source of truth"的React准则。

答案 1 :(得分:1)

您的问题是,调用setState不会立即更改状态-它等待代码完成并以新状态再次呈现组件。在计算total时,您依靠score进行更改,因此它不起作用。

有多种方法可以解决此问题-我认为score不应处于状态,而是在需要时根据totalrightAns计算得出的值。

答案 2 :(得分:1)

所有set ...函数都是异步的,不会立即更新值。因此,当您第一次渲染时,您使用right = 0和total = 0调用setScore(right / total),因此得到NaN作为得分的结果。其他所有问题都与使用错误值的setScore的同一问题有关。

解决此问题的一种方法是从状态中删除得分并将其添加到返回中,如下所示:

return (
    <>
    {total > 0 && <div>{right/total}</div>}
    <div>{rightAns}</div>
    <div>{total}</div>
    </>
)

您还可以简化您的使用效果:

useEffect(() => {
    setTotal(total + 1);
    if(result === true ) setRight(rightAns + 1);
}, [index]);

答案 3 :(得分:0)

关于当前的设置方式,您需要确保在索引之前更新结果。因为useEffect似乎正在围绕先前的结果创建一个闭合,并且将由此混乱。这表明它确实有效,您只需要确保在正确的时间更新结果和索引即可。 如果您不想在每次渲染时都计算分数(即,这是一个昂贵的计算),则可以使用Memo或useEffect,如我在stackblitz中所示。

https://stackblitz.com/edit/react-fughgt

尽管还有许多其他方法可以改善钩子的工作方式。一种是确保注意eslint react-hooks / exhaustive-deps规则,因为它会强制向您显示由于闭包的工作方式而最终可能发生的所有小错误。

在这种情况下,您可以轻松地基于total和rightAns计算分数。总数基本上只是index + 1

我也将修改使用效果,因为现在使用setState作为回调来摆脱其中的许多依赖关系问题:

useEffect(() => {
  if (result === true) {
    setRight(rightAns => rightAns + 1);
    setTotal(total => total + 1);
  }
  if (result === false) {
    setTotal(total => total + 1);
  }
}, [index]);
useEffect(()=>{
  setScore(rightAns / total ||0);
},[rightAns,total])