【问题标题】:Refactoring React Class component to Functional - TypeError: Cannot read properties of undefined (reading 'slice')将 React Class 组件重构为 Functional - TypeError:无法读取未定义的属性(读取“切片”)
【发布时间】:2021-10-07 14:09:47
【问题描述】:

我正在尝试将一个类组件重构为功能组件,但是当我单击其中一个骰子或滚动按钮时,我收到错误“TypeError: Cannot read properties of undefined (reading 'slice')”。

我将类组件Game.js重构为功能组件Game.js时出现的错误,不知道如何解决。如果有人可以向我解释,错误在哪里......

错误:

类组件 Game.js

import React from 'react';
import '../css/Game.css';
import Dice from './Dice';
import ScoreCard from './ScoreCard';
import { rollD, scoringFunctions } from '../helpers.js';



class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      rollsLeft: 2,
      dice: Array.from(Array(5)).map(i => ({value: rollD(), locked: false})),
      score: 0,
      upperBonus: false,
      yahtzeeBonus: 0,
      yahtzeeMode: false,
      scoreItems: [
        {name: 'Ones', score: null, description: 'Sum of all Ones'},
        {name: 'Twos', score: null, description: 'Sum of all Twos'},
        {name: 'Threes', score: null, description: 'Sum of all Threes'},
        {name: 'Fours', score: null, description: 'Sum of all Fours'},
        {name: 'Fives', score: null, description: 'Sum of all Fives'},
        {name: 'Sixes', score: null, description: 'Sum of all Sixes'},
        {name: '3 of a kind', score: null, description: 'Sum of all dice if 3 are the same'},
        {name: '4 of a kind', score: null, description: 'Sum of all dice if 4 are the same'},
        {name: 'Small Straight', score: null, description: '30 points for a small straight'},
        {name: 'Large Straight', score: null, description: '40 points for a large straight'},
        {name: 'Full House', score: null, description: '25 points for a full house'},
        {name: 'YAHTZEE', score: null, description: '50 points for yahtzee'},
        {name: 'Chance', score: null, description: 'Sum all dice'}
      ]
    }
    
    this.rollDice = this.rollDice.bind(this);
    this.resetRoll = this.resetRoll.bind(this);
    this.toggleDieLock = this.toggleDieLock.bind(this);
    this.handleScore = this.handleScore.bind(this);
    this.updateBonus = this.updateBonus.bind(this);
    this.checkUpperBonus = this.checkUpperBonus.bind(this);
    this.updateYahtzeeState = this.updateYahtzeeState.bind(this);
    this.isYahtzee = this.isYahtzee.bind(this);
  }
  
  rollDice() {
    const newDice = this.state.dice.map((die) => {
      return die.locked ? die : {...die, value: rollD()};
    })
    this.setState(prev => (
      {
        rollsLeft: prev.rollsLeft - 1,
        dice: newDice
      }), () => this.isYahtzee());
  }
  
  resetRoll() {
    this.setState(
      {
        dice: Array.from(Array(5)).map(i => ({value: rollD(), locked: false})),
        rollsLeft: 2
      }, () => this.isYahtzee()
    )
  }
  
  toggleDieLock(index) {
    this.setState((prev) => {
      return({
        dice: prev.dice.map((die, i) => {
          if(i === index) {
            return {...die, locked: !die.locked};
          } else {
            return die
          }
        })
      })
    })
  }
  
  isYahtzee() {
    let yahtzeeDice;
    for(let i = 0; i < this.state.dice.length - 1; i++) {
      if (this.state.dice[i].value !== this.state.dice[i + 1].value) {
        return this.setState({yahtzeeMode: false})
      }
    }
    if(this.state.yahtzeeBonus) this.setState({yahtzeeMode: true})
  }
  
  handleScore(name) {
    let scoreValue = scoringFunctions[name](this.state.dice, this.state.yahtzeeMode);
    let index;
    let yahtzeeIndex;
    for(let i = 0; i < this.state.scoreItems.length; i++) {
      if(this.state.scoreItems[i].name === name) {
        index = i;
      }
      if(this.state.scoreItems[i].name === 'YAHTZEE') {
        yahtzeeIndex = i;
      }
    }
    let updatedScoreItems = [...this.state.scoreItems];
    updatedScoreItems[index].score = scoreValue;
    this.setState((prev) => (
      {
        score: prev.score + scoreValue,
        scoreItems: updatedScoreItems
      }
    ), () => {this.updateBonus(yahtzeeIndex)})
    this.resetRoll();
  }
  
  updateBonus(yahtzeeIndex) {
    if(!this.state.upperBonus) this.checkUpperBonus();
    if(!this.state.yahtzeeBonus) {
      this.updateYahtzeeState(yahtzeeIndex);
    } else if(this.state.yahtzeeMode) {
      this.setState((prev) => (
        {
          score: prev.score + 100,
          yahtzeeBonus: prev.yahtzeeBonus + 1 
      }))
    }
  }
  
  checkUpperBonus() {
    const totalUpper = this.state.scoreItems.slice(0, 6).reduce((total, item) => {
      return item.score ? item.score + total : 0 + total;
    }, 0);
    if(totalUpper >= 63) {
      this.setState((prev) => (
        {
          upperBonus: true,
          score: prev.score + 35
        }
      ))
    }
  }
  
  updateYahtzeeState(yahtzeeIndex) {
    if(this.state.scoreItems[yahtzeeIndex].score) {
      this.setState((prev) => ({yahtzeeBonus: prev.yahtzeeBonus + 1}))
    }
  }
  
  render() {
    return (
      <div className="game">
        <div className="header">
          <div className="title">
            <h1 className="h1">Yahtzee!</h1>
          </div>
          <Dice 
            dice={this.state.dice}
            rollsLeft={this.state.rollsLeft}
            rollDice={this.rollDice}
            toggleDieLock={this.toggleDieLock}
            yahtzeeMode={this.state.yahtzeeMode}
          />
        </div>
        <ScoreCard 
          dice={this.state.dice} 
          upperBonus={this.state.upperBonus}
          yahtzeeBonus={this.state.yahtzeeBonus}
          resetRoll={this.resetRoll} 
          handleScore={this.handleScore}
          scoreItems={this.state.scoreItems}
        />
        <div className="score-header">
          <h2 className="score">{`Total Score: ${this.state.score}`}</h2>
        </div>
      </div>
    )
  }
}

export default Game;

功能组件 Game.js

import React, { useState } from 'react';
import '../css/Game.css';
import Dice from './Dice';
import ScoreCard from './ScoreCard';
import { rollD, scoringFunctions } from '../helpers.js';


function Game() {
    const [gameState, setGameState] = useState({
        rollsLeft: 2,
        dice: Array.from(Array(5)).map(i => ({value: rollD(), locked: false})),
        score: 0,
        upperBonus: false,
        yahtzeeBonus: 0,
        yahtzeeMode: false,
        scoreItems: [
            {name: 'Ones', score: null, description: 'Sum of all Ones'},
            {name: 'Twos', score: null, description: 'Sum of all Twos'},
            {name: 'Threes', score: null, description: 'Sum of all Threes'},
            {name: 'Fours', score: null, description: 'Sum of all Fours'},
            {name: 'Fives', score: null, description: 'Sum of all Fives'},
            {name: 'Sixes', score: null, description: 'Sum of all Sixes'},
            {name: '3 of a kind', score: null, description: 'Sum of all dice if 3 are the same'},
            {name: '4 of a kind', score: null, description: 'Sum of all dice if 4 are the same'},
            {name: 'Small Straight', score: null, description: '30 points for a small straight'},
            {name: 'Large Straight', score: null, description: '40 points for a large straight'},
            {name: 'Full House', score: null, description: '25 points for a full house'},
            {name: 'YAHTZEE', score: null, description: '50 points for yahtzee'},
            {name: 'Chance', score: null, description: 'Sum all dice'}
        ]
      }
 );


 function rollDice() {
    const newDice = gameState.dice.map((die) => {
      return die.locked ? die : {...die, value: rollD()};
    })
    setGameState(prev => (
      {
        rollsLeft: prev.rollsLeft - 1,
        dice: newDice
      }), () => isYahtzee());
  }
  

  function resetRoll() {
    setGameState(
      {
        dice: Array.from(Array(5)).map(i => ({value: rollD(), locked: false})),
        rollsLeft: 2
      }, () => isYahtzee()
    )
  }
 
  
  function toggleDieLock(index) {
    setGameState((prev) => {
      return({
        dice: prev.dice.map((die, i) => {
          if(i === index) {
            return {...die, locked: !die.locked};
          } else {
            return die
          }
        })
      })
    })
  }
  

  function isYahtzee() {
    let yahtzeeDice;
    for(let i = 0; i < gameState.dice.length - 1; i++) {
      if (gameState.dice[i].value !== gameState.dice[i + 1].value) {
        return setGameState({yahtzeeMode: false})
      }
    }
    if(gameState.yahtzeeBonus) setGameState({yahtzeeMode: true})
  }
  

  function handleScore(name) {
    let scoreValue = scoringFunctions[name](gameState.dice, gameState.yahtzeeMode);
    let index;
    let yahtzeeIndex;
    for(let i = 0; i < gameState.scoreItems.length; i++) {
      if(gameState.scoreItems[i].name === name) {
        index = i;
      }
      if(gameState.scoreItems[i].name === 'YAHTZEE') {
        yahtzeeIndex = i;
      }
    }
    let updatedScoreItems = [...gameState.scoreItems];
    updatedScoreItems[index].score = scoreValue;
    setGameState((prev) => (
      {
        score: prev.score + scoreValue,
        scoreItems: updatedScoreItems
      }
    ), () => {updateBonus(yahtzeeIndex)})
    resetRoll();
  }
  

  function updateBonus(yahtzeeIndex) {
    if(!gameState.upperBonus) checkUpperBonus();
    if(!gameState.yahtzeeBonus) {
      updateYahtzeeState(yahtzeeIndex);
    } else if(gameState.yahtzeeMode) {
        setGameState((prev) => (
        {
          score: prev.score + 100,
          yahtzeeBonus: prev.yahtzeeBonus + 1 
      }))
    }
  }
  
  
  function checkUpperBonus() {
    const totalUpper = gameState.scoreItems.slice(0, 6).reduce((total, item) => {
      return item.score ? item.score + total : 0 + total;
    }, 0);
    if(totalUpper >= 63) {
        setGameState((prev) => (
        {
          upperBonus: true,
          score: prev.score + 35
        }
      ))
    }
  }
  
  
  function updateYahtzeeState(yahtzeeIndex) {
    if(gameState.scoreItems[yahtzeeIndex].score) {
        setGameState((prev) => ({yahtzeeBonus: prev.yahtzeeBonus + 1}))
    }
  }
  

    return (
      <div className="game">
        <div className="header">
          <div className="title">
            <h1 className="h1">Yahtzee!</h1>
          </div>
          <Dice 
            dice={gameState.dice}
            rollsLeft={gameState.rollsLeft}
            rollDice={rollDice}
            toggleDieLock={toggleDieLock}
            yahtzeeMode={gameState.yahtzeeMode}
          />
        </div>
        <ScoreCard 
          dice={gameState.dice} 
          upperBonus={gameState.upperBonus}
          yahtzeeBonus={gameState.yahtzeeBonus}
          resetRoll={resetRoll} 
          handleScore={handleScore}
          scoreItems={gameState.scoreItems}
        />
        <div className="score-header">
          <h2 className="score">{`Total Score: ${gameState.score}`}</h2>
        </div>
      </div>
    )
  }


export default Game;

功能组件 ScoreCard.js

import React from 'react';
import '../css/ScoreCard.css';
import ScoreItem from './ScoreItem';


const ScoreCard = props => {

const { handleScore, scoreItems} = props;

  return <div className="scorecard">
            <div className="scorecard__header">
                <h2>Upper Section</h2>
            </div>
            {scoreItems.slice(0, 6).map(item => {
            return <ScoreItem 
            key={item.name} 
            name={item.name} 
            score={item.score} 
            description={item.description} 
            handleScore={handleScore} 
            />;
            })}
        <div className="scorecard__header">
          <h2>Lower Section</h2>
        </div>
            {scoreItems.slice(6, 13).map(item => {
            return <ScoreItem 
            key={item.name} 
            name={item.name} 
            score={item.score} 
            description={item.description} 
            handleScore={handleScore} 
            />;
            })}
      </div>;
};

export default ScoreCard;

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    看起来您的各种状态更新只是部分更新了对象。在 React 类组件中,setState 操作负责为您将新更新与状态对象的其余部分合并,但 useState 钩子并非如此。

    例如,您将状态设置为:

    {
      rollsLeft: prev.rollsLeft - 1,
      dice: newDice
    }
    

    你应该这样做:

    {
      ...prev,
      rollsLeft: prev.rollsLeft - 1,
      dice: newDice
    }
    

    这样,prev 中已有的所有属性都会在您为这些属性的子集提供新值之前添加。否则,任何未指定的属性在更新后最终会变为 undefined

    【讨论】:

    • 感谢大卫的帮助!骰子现在正在工作。现在我尝试保存结果时遇到问题,出现同样的错误。
    • @MiroslavB:如果这是一个单独的问题,那么它可能值得一个单独的问题。绝对鼓励您先进行一些调试并尝试缩小具体问题的范围。
    猜你喜欢
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多