React hooks 道具和状态通信问题

Rect hooks props and state communication problem

我正在尝试基于 React Hooks 构建我的第一个 React 应用程序,但我很难弄清楚我的 state/props 和重新渲染发生了什么。我正在制作一些小型猜谜游戏。

我有一个父组件 GameContent,它传递给它的子组件(Round)一些道具:

通过 'shiftRounds' 我告诉我的父组件:'this player gave correct answer',函数改变了 round 和 score(都在父状态)。

虽然看起来很简单,但我在子组件中的函数检查答案是否正确,总是从第一轮获取数据。

我尝试了React.useRef,尝试了不同的状态配置,但仍然找不到解决方案。

<GameContent />

const GameContent = ({ gameOptions, gameFinish }) => {

  const [gameData, setGameData] = useState([]);
  const [round, setRound] = useState(1);
  const [score, setScore] = useState([0, 0]);

  const [ligue, dateFrom, dateTo, rounds, player1, player2] = gameOptions;

  useEffect(() => {
    //
    // Function fetches football matches. 
    //
    (async () => {
      const matchesData = await fetchMatches(ligue, dateFrom, dateTo)
      setGameData(matchesData);
    })();
  }, [])

  const nextRound = (scoringPlayer) => {
    if (round <= rounds) {
      setScore(score => {
        score[scoringPlayer] += 1;
        return score
      });
      setRound(round => round + 1);
    } else {
      finishGame()
    }
  }

  return (
    <div className="text-center">
      <h2>Round {round}</h2>
      <h3>Score</h3>
      <h2>{`${score[0]} : ${score[1]}`}</h2>
      {(gameData.length) ? <Round currentRound={round} roundData={gameData[round - 1]} shiftRounds={nextRound} players={[player1, player2]} /> : <h1>Loading...</h1>}
    </div>
  )
}

<Round />

const Round = (props) => {
  const [isCheckingAnswer, setIsCheckingAnswer] = useState(false);

  useEffect(() => {
    //
    // Set eventListeners for keydown (too capture player's answer)
    //
    document.addEventListener('keydown', (e) => { handleKeyPress(e) });
    setIsCheckingAnswer(false);
    return () => {
      document.removeEventListener('keydown', (e) => { handleKeyPress(e) });
    }
  }, [props.roundData])

  const handleKeyPress = (e) => {
    if (!isCheckingAnswer) {
      e.preventDefault();
      setIsCheckingAnswer(true);
      if (keysMap[e.key] === checkGoodAnswer(props.roundData.homeTeamScore, props.roundData.awayTeamScore)) {
        const winingPlayer = isNaN(e.key / 2) ? 0 : 1;
        props.shiftRounds(winingPlayer);
      } else {
        setIsCheckingAnswer(false);
      }
    } else {
      return
    }
  }
  return (
    <>
      <Row>
        <Col xs={4}>
          <h2>{props.roundData.homeTeam}</h2>
        </Col>
        <Col xs={4}>
          <h2>vs</h2>
        </Col>
        <Col xs={4}>
          <h2>{props.roundData.awayTeam}</h2>
        </Col>
      </Row>
      <Row className="justify-content-center my-5">
        <Col xs={4}>
          <h3>{props.players[0]}</h3>
        </Col>
        <Col xs={4}>
          <h3>{props.players[1]}</h3>
        </Col>
      </Row>
    </>
  )
}

辅助函数

const checkGoodAnswer = (homeTeamScore, awayTeamScore) => {
  if (homeTeamScore > awayTeamScore) {
    return 'homeTeam'
  } else if (homeTeamScore < awayTeamScore) {
    return 'awayTeam'
  } else {
    return 'draw'
  }
}

const keysMap = {
  'a': 'homeTeam',
  'w': 'draw',
  'd': 'awayTeam',
  '4': 'homeTeam',
  '8': 'draw',
  '6': 'awayTeam',
}

获取的数据样本:

[
{awayTeam: "Paris Saint-Germain FC"
awayTeamScore: 2
homeTeam: "Manchester United FC"
homeTeamScore: 0},
{
awayTeam: "FC Porto"
awayTeamScore: 1
homeTeam: "AS Roma"
homeTeamScore: 2
},
...
]

您应该添加 ALL 相关的变量/函数,这些变量/函数在您的 useEffect 中作为依赖项使用。

来自docs

... make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect

例如,在您的第一个效果中,您的数组中没有依赖项:

useEffect(() => {
    //
    // Function fetches football matches. 
    //
    (async () => {
      const matchesData = await fetchMatches(ligue, dateFrom, dateTo)
      setGameData(matchesData);
    })();
  }, [])

但你确实使用了 liguedateFromdateTo 等...

react 团队提供了一个不错的 eslint 插件(eslint-plugin-react-hooks)可以帮助您解决此类问题,我建议您尝试一下。

我找到了解决方案。问题在于 addEventListener 中调用的函数范围, 我这样称呼它:

document.addEventListener('keydown', (e) => { handleKeyPress(e) });

但正确的调用方式是:

document.addEventListener('keydown', handleKeyPress);

Sagiv 还指出,在 useEffect 中,所有随时间变化并在内部使用的变量都应包含在数组中,如下所示:

 useEffect(() => {
      document.addEventListener('keydown', (e) => { handleKeyPress(e) });
      setIsCheckingAnswer(false);
      return () => {
        document.removeEventListener('keydown', (e) => { handleKeyPress(e) });
      }
    }, [props.roundData])