如何在React的useEffect中停止四子棋的倒计时?
四子棋倒计时停止问题解决方案
问题核心
你第一个useEffect的依赖数组为空,组件挂载后仅执行一次,showNotification的取值始终是初始的false,后续状态更新无法被定时器感知,导致判断逻辑完全失效。
修复方案
- 将
showNotification加入第一个useEffect的依赖数组,确保状态变化时重新创建/清理定时器 - 优化定时器逻辑:仅在未显示获胜通知时启动倒计时,获胜时直接终止定时器
修改后的完整代码
import React, { useEffect, useState } from 'react'; import './Table.css'; import Timer from './Timer'; import WinningNotification from './WinningNotification'; import { BsFillEmojiSmileFill } from 'react-icons/bs'; export default function Table() { const [table, setTable] = useState({ table: [ [null, null, null, null, null, null, null], [null, null, null, null, null, null, null], [null, null, null, null, null, null, null], [null, null, null, null, null, null, null], [null, null, null, null, null, null, null], [null, null, null, null, null, null, null], ], }); const [seconds, setSeconds] = useState(30); const [player, setPlayer] = useState(true); const [showNotification, setShowNotification] = useState(false); const [winnerColor, setWinnerColor] = useState(null); const [winningPlayerOne, setWinningPlayerOne] = useState(0); const [winningPlayerTwo, setWinningPlayerTwo] = useState(0); useEffect(() => { // 获胜时直接跳过定时器创建 if (showNotification) return; const interval = setInterval(() => { setSeconds(prev => prev - 1); }, 1000); return () => clearInterval(interval); }, [showNotification]); // 添加状态依赖 useEffect(() => { // 获胜时不执行玩家切换逻辑 if (seconds < 0 && !showNotification) { setPlayer(!player); setSeconds(30); } }, [seconds, showNotification, player]); function dropCoin(number) { let lastEmptyCell; let color; for (let i = 0; i < table.table.length; i++) { if (table.table[i][number] === null) continue; lastEmptyCell = i - 1; break; } lastEmptyCell = lastEmptyCell ?? table.table.length - 1; // 深拷贝表格,避免直接修改原状态 const newTable = [...table.table.map(row => [...row])]; color = player ? 'red' : 'yellow'; if (lastEmptyCell >= 0 && lastEmptyCell < newTable.length) { newTable[lastEmptyCell][number] = color; setTable({ table: newTable }); if (checkForWin(color, newTable)) { setShowNotification(true); setWinnerColor(color); color === 'red' ? setWinningPlayerOne(prev => prev + 1) : setWinningPlayerTwo(prev => prev + 1); return; // 获胜后终止函数,不切换玩家 } } setPlayer(!player); setSeconds(30); } function checkForWin(color, table) { // 检查横向 for (let i = 0; i < table.length; i++) { for (let j = 0; j <= table[i].length - 4; j++) { if (table[i][j] === color && table[i][j+1] === color && table[i][j+2] === color && table[i][j+3] === color) { return true; } } } // 检查纵向 for (let i = 0; i <= table.length - 4; i++) { for (let j = 0; j < table[i].length; j++) { if (table[i][j] === color && table[i+1][j] === color && table[i+2][j] === color && table[i+3][j] === color) { return true; } } } // 检查左上到右下对角线 for (let i = 0; i <= table.length - 4; i++) { for (let j = 0; j <= table[i].length - 4; j++) { if (table[i][j] === color && table[i+1][j+1] === color && table[i+2][j+2] === color && table[i+3][j+3] === color) { return true; } } } // 检查左下到右上对角线 for (let i = 4; i < table.length; i++) { for (let j = 0; j <= table[i].length - 4; j++) { if (table[i][j] === color && table[i-1][j+1] === color && table[i-2][j+2] === color && table[i-3][j+3] === color) { return true; } } } return false; } function restart() { setTable({ table: [ [null, null, null, null, null, null, null], [null, null, null, null, null, null, null], [null, null, null, null, null, null, null], [null, null, null, null, null, null, null], [null, null, null, null, null, null, null], [null, null, null, null, null, null, null], ], }); setPlayer(true); setSeconds(30); setShowNotification(false); setWinnerColor(null); } return ( <div className="container"> <button onClick={restart} className="restartButton">restart</button> <div className="playerone"> <p>Player 1 </p> <BsFillEmojiSmileFill style={{ color: 'red', fontSize: '40px' }} /> {winningPlayerOne} </div> <div className="playertwo"> <p>Player 2</p> <BsFillEmojiSmileFill style={{ color: 'yellow', fontSize: '40px' }} /> {winningPlayerTwo} </div> <div className="headerRow"> {[0,1,2,3,4,5,6].map(number => ( <div className="headcell" onClick={() => dropCoin(number)} key={number}></div> ))} </div> <div className="table"> {table.table.map((row, rowIndex) => ( <div key={rowIndex} className="row"> {row.map((element, columnIndex) => ( <div key={columnIndex} className={`cell ${element === 'red' ? 'cellRed' : ''} ${element === 'yellow' ? 'cellYellow' : ''}`}></div> ))} </div> ))} </div> <Timer seconds={seconds} player={player} /> <img src="/table.svg" className="pseudotable" alt="table"></img> {showNotification && ( <WinningNotification color={winnerColor} restart={restart} player={player} /> )} </div> ); }
额外优化说明
- 修复了
dropCoin中直接修改原状态的问题,改用深拷贝确保状态更新符合React规范 - 分数更新使用函数式更新,避免依赖旧状态值导致的计数错误
- 获胜后直接终止
dropCoin函数,防止不必要的玩家切换操作
内容的提问来源于stack exchange,提问作者Davidino
相关产品推荐
相关产品推荐

