井字棋案例可以在 react 官网上按步骤去逐步实现,但是官网上的教程还有一部分扩展是未实现的,有兴趣的可以尝试一下
案例代码地址:https://gitee.com/aolie/test_react
在游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号)
这个案例是实现这个需求 ,其实这个需求难度不大,只要注意一点就行,那就是,this.setState(),这个方法,在改变 state 中的数据之后,是不会立即生效的,如果需要立即调用这个数据的话,需要在内部写一个异步的回调函数去使用 更新后的数据
1、在点击单元格的时候,需要记录下当前点击单元格的坐标,但是需要注意的是,当我回到第一步还未开始的状态时,此时需要设置一个默认坐标,不然和步骤对不上,我默认给的是[0,0]
export default class Game extends Component {
constructor(props) {
super(props);
this.state = {
history: [{ squares: Array(9).fill(null) }],
xIsNext: true,
setNumber: 0,
coordinate: [[0, 0]] //初始位置默认为坐标[0,0]
};
}
}
2、在 Game.js 顶层父ji组件中,我们需要先记录所有单元格的坐标,以便与在点击的时候,添加到数组中,与历史记录步骤匹配。点击单元格的时候,会将该单元格的排序传递进来,根据排序找到当前点击单元格坐标,且将点击的坐标添加到历史坐标记录数组中
// 记录历史记录坐标
historyCoordinate(i) {
let Coordinate = [
[1, 1],
[1, 2],
[1, 3],
[2, 1],
[2, 2],
[2, 3],
[3, 1],
[3, 2],
[3, 3]
];
this.setState({
coordinate: [...this.state.coordinate, Coordinate[i]]
});
}
3、单元格点击事件扩展
需要分为两种情况,
第一种是,直接点击单元格,记录历史坐标。
第二种是,先点击单元格,记录历史坐标,然后点击历史记录,查看之前的游戏状态,且在此时再次点击单元格,改变游戏状态的同时记录新的游戏记录和点击坐标
// 顶层判断单元格点击事件
handleClick(i) {
// const history = this.state.history;
// 创建一个副本,避免改动原来的历史记录数据
// 这个副本保存的是之前的原始数据从头开始的到现在的最后一步,如果是第一次点击,其实就是history.length 如果是查看历史记录,那就是 当前查看步数
const history = this.state.history.slice(0, this.state.setNumber + 1);
// console.log(history);
// 获取当前点击的步骤,如果是第一次点击单元格,那么setNumber和length-1一样,如果是查看历史记录,那么就是当前查看的步数
const current = history[history.length - 1];
// 以上两步是属于取数据,这一步得到一个当前点击之后的 单元格状态副本,之所以用副本,是为了不改变历史数据
const squares = current.squares.slice();
if (this.calculateWinner(squares) || squares[i]) {
return;
}
// 这一步就是点击历史记录之后,需要将当前的历史记录与历史坐标对应,获取初始状态到当前记录的历史坐标
// 这一步是和上面的存取数据类似,也是用副本操作数据,当我在点击历史记录的时候,也是需要改变当前历史坐标的数据的,避免直接改变 state 中的数据,二是通过setState 去改变数据
const coordinate_1 = this.state.coordinate.slice(
0,
this.state.setNumber + 1
);
squares[i] = this.state.xIsNext ? "X" : "O";
this.setState(
{
history: history.concat([{ squares: squares }]),
xIsNext: !this.state.xIsNext,
setNumber: history.length,
coordinate: coordinate_1
},
state => {
// 这一步是关键点,当我点击之后,通过上面的 setState 重新改变了当前的历史坐标记录数组
// 但是并没有立即生效,如果我把这一步操作放在 stestate 外部,那么扩展运算符得到的还是改变之前的坐标数组
this.historyCoordinate(i);
}
);
console.log(this.state.coordinate);
}
4、render 渲染
const moves = history.map((step, index) => {
const desc = index ? "Go to move #" + index : "Go to game start";
return (
<li key={index}>
<button
onClick={() => {
this.jump(index);
}}
>
{`${desc}+${this.state.coordinate[index]}`}
</button>
</li>
);
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={i => {
this.handleClick(i);
}}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
{/* {this.historyMove} */}
</div>
</div>
);