почему useEffect отображает неожиданные значения? - PullRequest
0 голосов
/ 08 апреля 2020

Я пытаюсь создать табло для приложения викторины. После ответа на вопрос индекс обновляется. Вот код для компонента.

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 ]

3 голосов
/ 08 апреля 2020

Вы не должны хранить оценку в состоянии вообще, потому что она может быть рассчитана на основе других состояний.

Все вызовы изменения состояния являются асинхронными, и значения состояния не изменяются до тех пор, пока происходит повторное рендеринг, что означает, что вы по-прежнему получаете доступ к старым значениям.

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

Проще и следует рекомендациям React относительно единственного «источника истины» .

1 голос
/ 08 апреля 2020

Все ваши функции set ... являются асинхронными и не обновляют значение немедленно. Поэтому, когда вы впервые выполняете рендеринг, вы вызываете setScore (right / total) с right = 0 и total = 0, поэтому вы получаете 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]);
1 голос
/ 08 апреля 2020

Ваша проблема в том, что вызов setState не меняет состояние немедленно - он ждет, пока код не достигнет конечного значения sh, и снова визуализирует компонент с новым состоянием. Вы полагаетесь на total изменение при расчете score, поэтому оно не работает.

Существует несколько подходов для решения этой проблемы - на мой взгляд score должно быть не состоянием, а значением вычисляется из total и rightAns, когда вам это нужно.

0 голосов
/ 08 апреля 2020

Учитывая то, как он настроен в настоящее время, вам нужно будет убедиться, что вы обновляете результат перед индексированием. Потому что кажется, что useEffect создает замыкание вокруг предыдущего результата и будет портить его. Здесь показано, что это работает, вам просто нужно убедиться, что результат и индекс обновляются в нужное время. Если вы не хотите вычислять счет для каждого рендера (то есть это дорогостоящий расчет), вы можете использовать Memo или useEffect, как я показал в стекаблице.

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

Хотя есть много других способов улучшить работу с крючками. Один из них - обратить внимание на правило eslint реагировать-зацепки / исчерпывающее-deps, поскольку оно принудительно покажет вам все мелкие ошибки, которые могут произойти в результате работы замыканий.

В этом случае Вы можете легко рассчитать балл на основе общего и правого. И по сути всего просто 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])
...