You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React的useEffect中停止四子棋的倒计时?

四子棋倒计时停止问题解决方案

问题核心

你第一个useEffect的依赖数组为空,组件挂载后仅执行一次,showNotification的取值始终是初始的false,后续状态更新无法被定时器感知,导致判断逻辑完全失效。

修复方案

  1. 将showNotification加入第一个useEffect的依赖数组,确保状态变化时重新创建/清理定时器
  2. 优化定时器逻辑:仅在未显示获胜通知时启动倒计时,获胜时直接终止定时器

修改后的完整代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 08:42:38