React Native井字棋开发:history状态数组访问与回退功能实现问题求助
我帮你排查了代码里的几个关键问题,这些都是导致你无法正确回退到历史棋局步骤的原因,咱们一步步来解决:
1. 历史记录(history)的格式不统一
你初始的history是一个二维数组,但在onTilePress里往history里添加的是{arr}对象,这就导致数组里的元素类型混乱,后续读取的时候自然拿不到正确的棋局状态。另外,你直接用slice()拷贝二维数组是浅拷贝,修改内层数组会污染历史记录。
修复方案:
- 初始化
history时,把初始棋局和当前玩家状态包装成对象存入数组,保持所有元素格式一致; - 更新history时,对棋局状态做深拷贝,避免引用污染;
- 合并多次
setState调用,避免异步更新导致的状态不一致。
2. onGoPress函数的语法与逻辑错误
你的onGoPress不仅有语法错误(setState的参数必须是对象),还没有接收要回退的步骤索引,而且按钮绑定的时候直接执行了函数,不是点击触发。
修复方案:
- 让
onGoPress接收步骤索引参数,从history中取出对应的完整状态(棋局+当前玩家); - 按钮绑定用箭头函数包裹,确保点击时才执行函数。
3. 按钮绑定错误
你后面的“回退步骤”按钮都错误绑定了onNewGamePress,应该统一绑定onGoPress。
完整修改后的代码
import React, { Component } from 'react'; import { StyleSheet, Text, View, TouchableOpacity, Alert, Button, ScrollView } from 'react-native'; import { MaterialCommunityIcons as Icon } from 'react-native-vector-icons' export default class App extends React.Component { constructor(props) { super(props); const initialGameState = [ [0, 0, 0], [0, 0, 0], [0, 0, 0] ]; this.state = { gameState: initialGameState, currentPlayer: 1, history: [ { gameState: initialGameState, currentPlayer: 1 } ], }; } componentDidMount() { this.initializeGame(); } initializeGame = () => { const initialGameState = [ [0, 0, 0], [0, 0, 0], [0, 0, 0] ]; this.setState({ gameState: initialGameState, currentPlayer: 1, history: [{ gameState: initialGameState, currentPlayer: 1 }] }); } onGoPress = (index) => { // 做边界判断,防止索引越界 if (index < 0 || index >= this.state.history.length) { Alert.alert("Invalid move index!"); return; } const targetState = this.state.history[index]; this.setState({ gameState: targetState.gameState, currentPlayer: targetState.currentPlayer }); } onNewGamePress = () => { this.initializeGame(); } getWinner = () => { const num_tiles = 3; const arr = this.state.gameState; let sum; // 检查行 for (let i = 0; i < num_tiles; i++) { sum = arr[i][0] + arr[i][1] + arr[i][2]; if (sum === 3) return 1; else if (sum === -3) return -1; } // 检查列 for (let i = 0; i < num_tiles; i++) { sum = arr[0][i] + arr[1][i] + arr[2][i]; if (sum === 3) return 1; else if (sum === -3) return -1; } // 检查对角线 sum = arr[0][0] + arr[1][1] + arr[2][2]; if (sum === 3) return 1; else if (sum === -3) return -1; sum = arr[0][2] + arr[1][1] + arr[2][0]; if (sum === 3) return 1; else if (sum === -3) return -1; return 0; } onTilePress = (row, col) => { const currentPlayer = this.state.currentPlayer; const value = this.state.gameState[row][col]; // 如果格子已被占用,直接返回 if (value !== 0) return; // 深拷贝当前棋局状态,避免污染历史记录 const newGameState = this.state.gameState.map(row => [...row]); newGameState[row][col] = currentPlayer; const nextPlayer = currentPlayer === 1 ? -1 : 1; this.setState(prevState => ({ gameState: newGameState, currentPlayer: nextPlayer, // 将新的棋局状态和玩家状态存入历史记录 history: [...prevState.history, { gameState: newGameState, currentPlayer: nextPlayer }] }), () => { // 在setState完成后检查胜负 const winner = this.getWinner(); if (winner === 1) { Alert.alert("Player X is the winner"); this.initializeGame(); } else if (winner === -1) { Alert.alert("Player O is the winner"); this.initializeGame(); } }); } renderIcon = (row, col) => { const value = this.state.gameState[row][col]; switch (value) { case 1: return <Icon name="close" style={styles.tileX} /> case -1: return <Icon name="circle-outline" style={styles.tileO} /> default: return <View /> } } render() { return ( <ScrollView style={{ marginTop: 50 }}> <View style={styles.container}> <Text style={{ fontSize: 60, fontStyle: 'italic', paddingBottom: 30 }}>TIC TAC TOE</Text> <View style={{ flexDirection: 'row' }} > <TouchableOpacity onPress={() => this.onTilePress(0, 0)} style={styles.tile} > {this.renderIcon(0, 0)} </TouchableOpacity> <TouchableOpacity onPress={() => this.onTilePress(0, 1)} style={styles.tile} > {this.renderIcon(0, 1)} </TouchableOpacity> <TouchableOpacity onPress={() => this.onTilePress(0, 2)} style={styles.tile} > {this.renderIcon(0, 2)} </TouchableOpacity> </View> <View style={{ flexDirection: 'row' }} > <TouchableOpacity onPress={() => this.onTilePress(1, 0)} style={styles.tile} > {this.renderIcon(1, 0)} </TouchableOpacity> <TouchableOpacity onPress={() => this.onTilePress(1, 1)} style={styles.tile} > {this.renderIcon(1, 1)} </TouchableOpacity> <TouchableOpacity onPress={() => this.onTilePress(1, 2)} style={styles.tile} > {this.renderIcon(1, 2)} </TouchableOpacity> </View> <View style={{ flexDirection: 'row' }} > <TouchableOpacity onPress={() => this.onTilePress(2, 0)} style={styles.tile} > {this.renderIcon(2, 0)} </TouchableOpacity> <TouchableOpacity onPress={() => this.onTilePress(2, 1)} style={styles.tile} > {this.renderIcon(2, 1)} </TouchableOpacity> <TouchableOpacity onPress={() => this.onTilePress(2, 2)} style={styles.tile} > {this.renderIcon(2, 2)} </TouchableOpacity> </View> <View style={{ paddingTop: 80 }}></View> <View style={styles.button}><Button title="NEW GAME " onPress={this.onNewGamePress} /></View> <View style={styles.button}><Button title="Go to move no: 1" onPress={() => this.onGoPress(1)} /></View> <View style={styles.button}><Button title="Go to move no: 2" onPress={() => this.onGoPress(2)} /></View> <View style={styles.button}><Button title="Go to move no: 3" onPress={() => this.onGoPress(3)} /></View> <View style={styles.button}><Button title="Go to move no: 4" onPress={() => this.onGoPress(4)} /></View> <View style={styles.button}><Button title="Go to move no: 5" onPress={() => this.onGoPress(5)} /></View> <View style={styles.button}><Button title="Go to move no: 6" onPress={() => this.onGoPress(6)} /></View> <View style={styles.button}><Button title="Go to move no: 7" onPress={() => this.onGoPress(7)} /></View> <View style={styles.button}><Button title="Go to move no: 8" onPress={() => this.onGoPress(8)} /></View> <View style={styles.button}><Button title="Go to move no: 9" onPress={() => this.onGoPress(9)} /></View> </View> </ScrollView> ); } } // 自定义样式,可根据需求调整 const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center' }, tile: { borderWidth: 2, width: 100, height: 100, alignItems: 'center', justifyContent: 'center' }, tileX: { fontSize: 60, color: '#ff3b30' }, tileO: { fontSize: 60, color: '#007aff' }, button: { marginVertical: 5, width: 200 } });
额外说明
- 我添加了深拷贝棋局的逻辑,确保历史记录不会被后续修改污染;
- 在
setState的回调里检查胜负,保证拿到的是最新的棋局状态; - 给
onGoPress加了边界判断,防止用户点击不存在的步骤; - 补充了缺失的
styles样式,你可以根据自己的需求调整。
内容的提问来源于stack exchange,提问作者Sachidananda MG
相关产品推荐
相关产品推荐

