当前位置: 首页 > java >正文

使用react编写一个简单的井字棋游戏

用React实现井棋棋游戏:从状态管理到时间旅行功能详解

引言

井字棋是学习React状态管理和组件交互的经典案例。本文将逐行解析一个完整实现,包含历史回溯功能胜者判定逻辑,即使是React初学者也能彻底理解。


核心组件架构
Game组件
Board棋盘
历史记录
Square格子

1. Square组件:最基础的交互单元
function Square({ value, onSquareClick }) {return (<button onClick={onSquareClick}>{value} </button>);
}
  • 功能解析:
    • value:接收父组件传递的X/O/null
    • onSquareClick:点击时触发父组件的回调函数
    • 设计要点:组件本身不维护状态,由父组件完全控制(受控组件)

2. Board组件:游戏逻辑核心
function Board({ xIsNext, square, onPlay }) {// 处理格子点击事件function handleClick(i) {// 存在胜者或格子已被占用时阻止操作if (calculateWinner(square) || square[i]) return;const nextSquares = square.slice(); // 浅拷贝当前棋盘状态nextSquares[i] = xIsNext ? 'X' : 'O'; // 设置当前玩家符号onPlay(nextSquares); // 传递新状态给父组件}// 判断胜负状态const winner = calculateWinner(square);const status = winner ? `Winner: ${winner}` : `Next player: ${xIsNext ? 'X' : 'O'}`;// 渲染9宫格棋盘return (<><div className="status">{status}</div>{[0, 3, 6].map((rowStart) => (<div key={rowStart} className="board-row">{[0, 1, 2].map((offset) => {const index = rowStart + offset;return (<Square key={index}value={square[index]}onSquareClick={() => handleClick(index)}/>);})}</div>))}</>);
}

关键逻辑解析

  1. 点击处理流程
    • 通过square[i]检查格子是否为空
    • 使用slice()浅拷贝数组避免直接修改状态
    • 根据xIsNext决定放置XO
  2. 状态提升(Lifting State Up)
    • 通过onPlay将新状态回调给Game组件
    • 符合React单向数据流原则

3. Game组件:全局状态管理
export default function Game() {// 历史记录保存所有棋盘状态const [history, setHistory] = useState([Array(9).fill(null)]);// 当前查看的历史步骤const [currentMove, setCurrentMove] = useState(0);// 派生状态const xIsNext = currentMove % 2 === 0;const currentSquare = history[currentMove];// 处理棋盘状态更新function handlePlay(nextSquares) {// 裁剪历史记录(用于时间旅行后重新落子)const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];setHistory(nextHistory);setCurrentMove(nextHistory.length - 1);}// 时间旅行:跳转到指定历史步骤function jumpTo(nextMove) {setCurrentMove(nextMove);}// 生成历史步骤按钮const moves = history.map((squares, move) => {const desc = move > 0 ? `Go to move #${move}` : 'Go to game start';return (<li key={move}><button onClick={() => jumpTo(move)}>{desc}</button></li>);});return (<div className="game"><div className="game-board"><Board xIsNext={xIsNext}square={currentSquare}onPlay={handlePlay}/></div><div className="game-info"><ol>{moves}</ol></div></div>);
}

核心机制解析

  1. 历史记录管理
    • history:存储棋盘状态数组的数组
    • currentMove:当前展示的历史步骤索引
  2. 时间旅行实现原理
    • 点击历史按钮时jumpTo修改currentMove
    • 渲染对应历史索引的状态:currentSquare = history[currentMove]
    • 重新落子时裁剪历史分支
      history.slice(0, currentMove + 1)确保历史线性的正确性

4. 胜者判定算法
function calculateWinner(squares) {// 8种获胜组合(三行/三列/两对角线)const lines = [[0, 1, 2], [3, 4, 5], [6, 7, 8], // 行[0, 3, 6], [1, 4, 7], [2, 5, 8], // 列[0, 4, 8], [2, 4, 6]             // 对角线];for (const [a, b, c] of lines) {if (squares[a] &&squares[a] === squares[b] &&squares[a] === squares[c]) {return squares[a]; // 返回胜者符号'X'/'O'}}return null; // 无胜者
}

算法特点

  • 时间复杂度:O(1)(固定8种判断)
  • 返回值'X'/'O'(胜者)或null(未结束)
  • 空值检查squares[a] &&防止访问未填充格子

总结
  1. 组件层级清晰

    • Game(状态容器)→ Board(逻辑核心)→ Square(UI展示)
  2. 符合React最佳实践

    • 状态提升:子组件通过回调修改父组件状态
    • 不可变数据:使用slice()创建新数组
    • 派生状态:xIsNextcurrentMove计算得出
  3. 进阶功能实现

    • 时间旅行:通过历史索引切换棋盘状态
    • 历史裁剪:重新落子时自动清理分叉历史
  4. 可扩展方向

    当前实现
    添加移动高亮
    显示每步坐标
    平局判定

通过这个实现,我们展示了React的核心概念:组件化状态提升不可变数据派生状态。即使从未接触过React,按照组件分解的思维方式也能逐步理解整个应用的数据流动和交互逻辑。

http://www.xdnf.cn/news/16061.html

相关文章:

  • nodejs模块化
  • JS WebAPIs DOM节点概述
  • 前端_Javascript复习
  • C语言:第11天笔记
  • Python通关秘籍(四)数据结构——列表
  • 力扣 hot100 Day52
  • 网络基础DAY16-MSTP-VRRP
  • 2025 年最新 AI 技术:全景洞察与深度解析​
  • 02-netty基础-java四种IO模型
  • 深入解析 Spark:关键问题与答案汇总
  • 【Spring拦截器实战】路径拦截与访问控制系统设计
  • 期货配资软件开发注意事项?
  • Linux文件——文件系统Ext2(1)_理解硬件
  • Java (Spring AI) 实现MCP server实现数据库的智能问答
  • 2️⃣tuple(元组)速查表
  • 从“点状用例”到“质量生态”:现代软件测试的演进、困局与破局
  • vscode不识别vsix结尾的插件怎么解决?
  • 应用层攻防启示录:HTTP/HTTPS攻击的精准拦截之道
  • Datawhale AI 夏令营-心理健康Agent开发学习-Task1
  • MongoDB频繁掉线频繁断开服务的核心原因以及解决方案-卓伊凡|贝贝|莉莉|糖果
  • 【OpenCV篇】OpenCV——01day.图像基础
  • 漫画版:细说金仓数据库
  • 2025年COR SCI2区,基于多种配送模式的无人机自主配送车辆路径问题,深度解析+性能实测
  • 面试高频题 力扣 LCR 130.衣柜整理 洪水灌溉(FloodFill) 深度优先遍历(dfs) 暴力搜索 C++解题思路 每日一题
  • PACKET_HOST等宏定义介绍
  • 目标检测系列(六)labelstudio实现自动化标注
  • YOLO-实例分割头
  • 使用vue-pdf-embed发现某些文件不显示内容
  • 能协调控制器的硬件与软件组成及解决方案
  • 16.多生成树MSTP