Второй вызов setState предотвращает выполнение первого вызова setState - PullRequest
1 голос
/ 08 июля 2019

Я играю в камень, ножницы, бумагу.В приведенном ниже коде у меня есть файл контекста, который используется для хранения глобального состояния.Я также показываю свой компонент выбора.Когда пользователь нажимает кнопку в компоненте выбора, вызывается метод setChoices, который должен установить переменные выбора пользователя и процессора в глобальном состоянии.Затем запускается метод cpuScore () для увеличения показателя процессора (просто для иллюстрации проблемы).Оценка процессора обновляется, как и ожидалось, но переменные выбора не обновляются.Если я не запускаю метод cpuScore, переменные выбора обновляются, как и ожидалось, но, очевидно, не оценка.

// контекстный файл

import React, { createContext, useState } from 'react';

const GameContext = createContext();

const GameContextProvider = props => {

     const [gameItems, setGameItems] = useState(
        {userChoice: null, userScore: 0, cpuChoice: null, cpuScore: 0}
    );

    const setChoices = (userChoice, cpuChoice) => {
        setGameItems({...gameItems, userChoice: userChoice, cpuChoice: cpuChoice})
    }

    const cpuScore = () => {
        setGameItems({...gameItems, cpuScore: gameItems.cpuScore + 1})
    }

    return (
        <GameContext.Provider value={{gameItems, setChoices, cpuScore}}>
            { props.children }
        </GameContext.Provider>
    )
}
export default GameContextProvider;

// компонент выбора

import React, { useContext } from 'react';
import { GameContext } from '../contexts/GameContext';
const Choices = (props) => {

    const {  setChoices, cpuScore } = useContext(GameContext);

    const getCpuChoice = () => {
        const cpuChoices = ['r', 'p', 's'];
        const randomIndex = Math.floor((Math.random() * 3));
        const cpuDecision = cpuChoices[randomIndex];
        return cpuDecision
}

const playGame = (e) => {
    const userChoice = e.target.id;
    const cpuChoice = getCpuChoice();

    setChoices(userChoice, cpuChoice);
    cpuScore();

} 
return (
        <div>
            <h1>Make Your Selection</h1>
            <div className="choices">
                <i className="choice fas fa-hand-paper fa-10x" id="p" onClick={playGame}></i>
                <i className="choice fas fa-hand-rock fa-10x" id="r" onClick={playGame}></i>
                <i className="choice fas fa-hand-scissors fa-10x" id='s' onClick={playGame}></i>
            </div>
        </div>
    )

Что мне нужно изменить, чтобы установить состояние для выбора и оценки?

Ответы [ 2 ]

2 голосов
/ 08 июля 2019

Поскольку вызовы setState являются асинхронными, ваши два вызова setState мешают друг другу, более поздний перезаписывает предыдущий.

У вас есть несколько вариантов.

  1. Разделите ваше состояние, чтобы значения не влияли друг на друга:
const [choices, setChoices] = useState({ user: null, cpu: null });
const [scores, setScores] = useState({ user: 0, cpu: 0);

Или пойти еще дальше и установить каждый из двух вариантов и двух баллов в качестве собственного значения состояния.

  1. Сохранить все состояния в одном объекте, но обновить все сразу:
  const setChoices = (userChoice, cpuChoice) => {
    const cpuScore = gameItems.cpuScore + 1;
    setGameItems({
      ...gameItems,
      userChoice,
      cpuChoice,
      cpuScore
    });
  }
  1. Использование useReducer:
  const initialState = {
    userChoice: null,
    userScore: 0,
    cpuChoice: null,
    cpuScore: 0
  }

  const [gameItems, dispatch] = useReducer((state, action) => {
      switch (action.type) {
        case "UPDATE_CHOICES":
          return {
            ...state,
            userChoice: action.userChoice,
            cpuChoice: action.cpuChoice
          };
        case 'UPDATE_CPU_SCORE':
          return {
            ...state,
            cpuScore: state.cpuScore + 1
          }
        default:
          return state;
      }
  }, initialState);

  const setChoices = (userChoice, cpuChoice) => {
    dispatch({ type: 'UPDATE_CHOICES', userChoice, cpuChoice });
  };

  const cpuScore = () => {
    dispatch({ type: 'UPDATE_CPU_SCORE'})
  };
0 голосов
/ 08 июля 2019

По сути, React не обновляется сразу после вызова setState.

Если вы вызываете cpuScore() сразу после setChoices(), функция cpuScore, которую вы вызываете, остается функцией изтекущий рендер, а не после setChoices() обновления.Из-за этого cpuScore() снова установит состояние, используя значение спреда gameItems (которое все еще не изменилось, поскольку обновление из setChoices не вступило в силу), что приводит к изменению на setChoices()переопределены.

Если React всегда немедленно обновляется после каждого setState вызова, производительность будет ужасной.Что React делает, так это объединяет несколько вызовов setState в одно обновление, поэтому он не обновляет DOM повторно.

const cpuScore = () => {
    setGameItems({...gameItems, cpuScore: gameItems.cpuScore + 1})
}

Я бы предложил разделить эти два состояния, чтобы они нене могут быть перезаписаны друг другом или создать новую функцию, которая обрабатывает все эти обновления в одном месте.

...