阵列状态不会使用Redux挂钩立即更新

时间:2020-02-17 15:51:37

标签: javascript reactjs redux react-hooks

我正在重构我的Simon游戏应用程序以使用Redux钩子,但是,我的Redux存储中的userPattern数组的状态不会立即更新;在用户选择了应该复制的图案中的其他颜色后,它会更新。例如,如果用户处于级别1,则userPattern不会更新,直到用户选择了两种颜色。 userPattern的console.log有助于我确认userPattern的状态在选择第二种颜色之前不会更新。

下面,我将发布我的代码。请注意,当Button.js和App.js是类组件(Buttons.js始终是功能组件)时,该程序可以工作。提前谢谢你的帮助。 (如果您发现我发布的与此类似的问题,请忽略它。我无意中将其关闭了。)

App.js

import React, {useState, useEffect} from 'react';
import './App.css';
import './styles/styles.scss';
import red from './sounds/red.mp3'
import blue from './sounds/blue.mp3'
import green from './sounds/green.mp3'
import yellow from './sounds/yellow.mp3'
import wrong from './sounds/wrong.mp3'
import Buttons from './Buttons/Buttons'
import { useDispatch, useSelector, shallowEqual} from 'react-redux';
import { toggleGameOver, updateLevel, turnOnReadyForUserInput, turnOffReadyForUserInput, toggleSrtictMode, resetSimonGame, updateGamePattern, toggleGameStarted, setActiveStyle, emptyUserPattern, updateLastColor, turnOnUserIsWrong, turnOffUserIsWrong, setPlayerLevel } from './actions/actions'

var intervalRepeatSequence;
const App = () => {

  const [colors, setColors] = useState(["red","blue","green","yellow"]);
  const [sounds, setSounds] = useState({
                              red: new Audio(red),
                              blue: new Audio(blue),
                              green: new Audio(green),
                              yellow: new Audio(yellow),
                              wrong: new Audio(wrong)
                            })

  const dispatch = useDispatch();
  const readyForUserInput = useSelector(state => state.readyForUserInput);
  const level = useSelector(state => state.level);
  const gameStarted = useSelector(state => state.gameStarted);
  const gamePattern = useSelector(state => state.gamePattern);
  const userPattern = useSelector(state => state.userPattern);
  const userIsWrong = useSelector(state => state.userIsWrong);
  const strictMode = useSelector(state => state.strictMode);
  const strictRestart = useSelector(state => state.strictRestart);
  const gameOver = useSelector(state => state.gameOver);

  const handleNewSequence = () => {
    let randomColor = colors[Math.floor(Math.random() * 4)];

    console.log("gameStarted " + gameStarted)
    const currentLevel = gameStarted ? level : level + 1;

    //Adding new color to game pattern
    dispatch(updateGamePattern([...gamePattern, randomColor]));
    dispatch(updateLastColor(randomColor));
    dispatch(setPlayerLevel({level: currentLevel}));
    if (!gameStarted){
      dispatch(toggleGameStarted());
    }

    fadeInFadeOut(randomColor);

    //used for prevent user from picking colors while the sequence is being shown to them
    setTimeout(() => {
      dispatch(turnOnReadyForUserInput())
    }, 500)
    }    

//when the user picks an incorrect color, the background will change to red until gameOver is false again

  useEffect(() => {
    if (gameOver){
      document.body.style.background = "red";
    } else {
      document.body.style.background = "linear-gradient(to right, #FC466B , #3F5EFB)"
    }
  },[gameOver])

//handles the fade in and fade out animations when the user is being shown the sequence they must replicate
const fadeInFadeOut = (color) => {

  dispatch(setActiveStyle('fadeOut '));

  //after half a second the fade in animation will occur
  setTimeout(() => {
    dispatch(setActiveStyle('fadeIn '));
    dispatch(updateLastColor())
  }, 500)
  sounds[color].play();
}

const handleInput = () => { 

  let initialCheck = setTimeout(() => {

    let index = userPattern.length - 1;
    //compares elements in regular mode
    if ((userPattern[index] !== gamePattern[index]) && !strictMode) {
      dispatch(turnOffReadyForUserInput());
      verifyUserMoves();
    }

    // compares elements in both strict mode and regular mode when the user has input all the colors; basically used to check if the user 
    // got the last in the pattern correct, since they've gotten the rest of the pattern correct
    console.log('userPattern ' + userPattern)
    if ((userPattern.length === gamePattern.length) && !strictRestart) {
      //disables buttons when user finishes their turn
      console.log("In handleInput")
        dispatch(turnOffReadyForUserInput());
        verifyUserMoves();
    }
  }, 100)

    // used to validate patterns when strict mode is turned on
    setTimeout(() => {
      if (strictMode){
        let index = userPattern.length - 1;
        if (userPattern[index] !== gamePattern[index])
        {
          // Prevents the initial check from occuring because at this point we know the user has already input the incorrect pattern
          clearTimeout(initialCheck);
          dispatch(turnOffReadyForUserInput());
          verifyUserMoves();
        }
      }
    },50)
}

// if regular mode is on and the user is incorrect or correct, the sequence will just be replayed;
// the userIsWrong property will be used later in repeatSequence to determine whether a new color should
// be added to the sequence or not
const verifyUserMoves = () => {
  if (userPattern.join() === gamePattern.join()){
    dispatch(updateLevel());
    console.log('updated level')
    repeatSequence();
  } else {
    if (strictMode){
      wrongAnswer()
      setTimeout(() => {
        resetGame();
      },500)
    } else {
      wrongAnswer();
      repeatSequence();
    }
  }
}

const wrongAnswer = () => {
  dispatch(toggleGameOver());

  sounds["wrong"].play();

  //a delay is used to so that the header will return back to "Click me to begin game" or the current level of the game
  //and to return the background to normal
  setTimeout(() => {
    dispatch(toggleGameOver());
    dispatch(turnOnUserIsWrong());
  },500)
}

const repeatSequence = () => {
  dispatch(emptyUserPattern())

  var index = -1;

  // this will iterate through the colors and use the fadeInFadeOut function to apply the animations
  intervalRepeatSequence = setInterval(() => {
    index++;
    dispatch(updateLastColor());

    if (index <= gamePattern.length -1){
      let currentColor = gamePattern[index];

      setTimeout(()=> {
        dispatch(updateLastColor(currentColor));
        fadeInFadeOut(currentColor);
      },100)
    } else {
      //makes sure the current iteration won't coincide with the next
      clearInterval(intervalRepeatSequence);

      //if the user isn't wrong a new color is added to the game pattern
      if(!userIsWrong){
        setTimeout(()=>{
          handleNewSequence();
        },100)
      }

      if (userIsWrong) {
        dispatch(turnOnReadyForUserInput());
        dispatch(turnOffUserIsWrong());
      }
    }
  },1000)
}

// resets the game
const resetGame = () => {
  clearInterval(intervalRepeatSequence);
  dispatch(resetSimonGame());

}

// turns on strict mode
const handleStrictToggle = () => {
  resetGame();
  dispatch(toggleSrtictMode());
}
    return(
      <div 
        className={!readyForUserInput && level > 0 ? "pointer-events-disabled" : null}>
          <h1 
            className="header"
            onClick={level === 0 ? handleNewSequence : null}>{!gameStarted && level === 0 ? "Click me to begin game" : gameStarted && gameOver ? "Wrong. Please try again" : level > 0 ? "Level: " + level : null}</h1>
          <p 
            className={!strictMode ? "subheader" : "subheaderClicked"} 
            onClick={handleStrictToggle}>Strict Mode</p>
          <p className={"subheader"} onClick={resetGame}>Reset</p>
          <div>
            <div>
              <Buttons colors={colors} sounds={sounds} handleInput={handleInput}/>
            </div>
          </div>
      </div>
    )
  }

export default App;

Buttons.js

import React from 'react';
import Button from '../Button'

// Just a presentational component which contains the buttons
const Buttons = (props) => {
    return (
        <div>
            <div>
                {props.colors.slice(0,2).map(color => {
                    return(
                        <Button id={color} key={color} sound={props.sounds[color]} handleInput={props.handleInput}/>
                    )
                })}
            </div>
            <div>
                {props.colors.slice(2,4).map(color => {
                    return(
                        <Button id={color} key={color} sound={props.sounds[color]} handleInput={props.handleInput}/>
                    )
                })}
            </div>
        </div>
    )
}

export default Buttons;

Button.js

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { togglePressed } from './actions/actions'
import { updateUserPattern } from './actions/actions'



const Button = ({id, sound, handleInput}) => {
    const dispatch = useDispatch();
    const lastColor = useSelector(state => state.lastColor);
    const activeStyle = useSelector(state => state.activeStyle);
    const readyForUserInput = useSelector(state => state.readyForUserInput);
    const pressed = useSelector(state => state.pressed)
    const userPattern = useSelector(state => state.userPattern)

    const chooseColor = () => {
        if (readyForUserInput){
            sound.pause();
            sound.currentTime = 0;
            dispatch(updateUserPattern(id))
            console.log(`userPattern ` + userPattern)
            dispatch(togglePressed(id))
            setTimeout(() => {
                dispatch(togglePressed())
            },100)
            sound.play();
            handleInput();
        }   
    }

    return (
        <div className={lastColor === id ? 
            activeStyle + id + " button pointer-events-disabled" : 
        !readyForUserInput ? id + " button pointer-events-disabled" : 
        pressed === id ?
        // When you click on the button it will light up and handleInput will be called in App.js and determine how the input should be interpretted
        id + " button pressed" : id + " button"} onClick={chooseColor}></div>
    )

}

export default Button;

reducer.js

    const simonReduceDefaultState = {
    gamePattern: [],
    //the pattern that the user inputs
    userPattern: [],
    //used to indicate the current color in the sequence; used for fade in and fade out animation
    lastColor: "",
    //keeps track of the current level
    level: 0,
    //indicates whether game is over or not
    gameOver: false,
    //indicates if game has started
    gameStarted: false,
    //indicates when the user has input the wrong pattern
    userIsWrong: false,
    //indicates when the game will allow the user to pick colors for their pattern
    readyForUserInput: false,
    //used to track whether strict mode (if the user picks a wrong color the game resets) is on or not
    strictMode: false,
    //used to see whether if a strict restart has occured; allows us to figure out whether the users pattern is incorrect when they
    //get past level 1 
    strictRestart: false,
    //used to add light-up border animation
    pressed: '',
    //used to apply fade in, fade out animation
    activeStyle: '',
}

export default (state = simonReduceDefaultState, action) => {
    switch (action.type) {
        case 'SET_ACTIVE_STYLE':
            return {
                ...state,
                activeStyle: action.style
            }
        case 'UPDATE_LAST_COLOR':
            return {
                ...state,
                lastColor: action.color
            }
        case 'UPDATE_USER_PATTERN':
            return {
                ...state,
                userPattern: [...state.userPattern, action.id]
            }
        case 'UPDATE_GAME_PATTERN':
            return {
                ...state,
                gamePattern: [...action.newGamePattern]
            }
        case 'TOGGLE_PRESSED':
            console.log(action.color)
            return {
                ...state,
                pressed: action.color
            }
        case 'TURN_ON_READY_FOR_USER_INPUT':
            return {
                ...state,
                readyForUserInput: true
            }
        case 'TURN_OFF_READY_FOR_USER_INPUT':
            return {
                ...state,
                readyForUserInput: false
            }
        case 'RESET_GAME':
            return {
                ...state,
                gamePattern: [],
                userPattern: [],
                lastColor: "",
                level: 0,
                gameStarted: false,
                userIsWrong: false,
                readyForUserInput: false,
                activeStyle: '',
                strictRestart: false
            }
        case 'UPDATE_LEVEL':
            return {
                ...state,
                level: state.level + action.level
            }
        case 'TURN_OFF_USER_IS_WRONG':
            return{
                ...state,
                userIsWrong: false
            }
        case 'TURN_ON_USER_IS_WRONG':
                return{
                    ...state,
                    userIsWrong: true
                }
        case 'TOGGLE_STRICT_MODE':
            return {
                ...state,
                strictMode: !state.strictMode
            }
        case 'TOGGLE_GAME_STARTED':
            return {
                ...state,
                gameStarted: !state.gameStarted
            }
        case 'TOGGLE_GAME_OVER':
            return {
                ...state,
                gameOver: !state.gameOver
            }
        case 'EMPTY_USER_PATTERN':
            return {
                ...state,
                userPattern: []
            }
        case 'SET_PLAYER_LEVEL':
            return{
                ...state,
                level: action.level
            }
        default:
            return {
                ...state
            };     
    }
}

操作

    export const setActiveStyle = (style = '') => ({
    type: 'SET_ACTIVE_STYLE',
    style
})

export const updateLastColor = (color = '') => ({
    type:'UPDATE_LAST_COLOR',
    color
})

export const updateUserPattern = (id  = '') => ({
    type: 'UPDATE_USER_PATTERN',
    id
})

export const updateGamePattern = (newGamePattern =[]) => ({
    type: 'UPDATE_GAME_PATTERN',
    newGamePattern
})

export const togglePressed = (color = '') => ({
    type: 'TOGGLE_PRESSED',
    color
})

export const turnOnReadyForUserInput = () => ({
    type: 'TURN_ON_READY_FOR_USER_INPUT'
})

export const turnOffReadyForUserInput = () => ({
    type: 'TURN_OFF_READY_FOR_USER_INPUT'
})

export const resetSimonGame = () => ({
    type: 'RESET_GAME'
})

export const updateLevel = ({level = 1} = {}) => ({
    type: 'UPDATE_LEVEL',
    level
})

export const setPlayerLevel = ({level} = {}) => ({
    type: 'SET_PLAYER_LEVEL',
    level
})

export const toggleStrictRestart = () => ({
    type: "TOGGLE_STRICT_RESTART"
})

export const toggleSrtictMode = () => ({
    type: 'TOGGLE_STRICT_MODE'
})

export const toggleGameStarted = () => ({
    type: 'TOGGLE_GAME_STARTED'
})

export const toggleGameOver = () => ({
    type: 'TOGGLE_GAME_OVER'
})

export const emptyUserPattern = () => ({
    type: 'EMPTY_USER_PATTERN'
})

export const turnOffUserIsWrong = () => ({
    type: 'TURN_OFF_USER_IS_WRONG'
})

export const turnOnUserIsWrong = () => ({
    type: 'TURN_ON_USER_IS_WRONG'
})

0 个答案:

没有答案