前言:延接上文继续,将最后的game组件讲完
我在之前那篇文章中说过game的作用是用来存储board的每一个历史状态的,那么具体放在游戏中可以用来做什么,保存下每一个历史状态,其实意味着悔棋(哈哈哈)在上一篇文章中,为了保留每一个square的状态,我们用到了状态提升,将每一个square的状态交给了他的管理者(老大)board管理,这里其实也是同样的道理,我们将board的状态及交互交给game组件来实现
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [{
squares: Array(9).fill(null),
}],
xIsNext: true,
};
}
history数组存储board状态,和上一篇文章中讲到的很像,此外,board除了交出自己的状态以外,它拥有的管理权也应该全部上交(点击事件)
renderSquare(i) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(i)}
/>
);
}
以上代码是实现的board将click事件与状态进行提升以后的处理方法,board 的render方法也不应该再去判断输赢条件了应该交给game组件去判断:
render() {
return (
<div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
handleClick(i) {
const history = this.state.history;
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{
squares: squares,
}]),
xIsNext: !this.state.xIsNext,
});
}
第一段代码是交权,第二段代码是收到权力
我们已经将状态的存储这一问题解决掉了,下面我们要处理的是我们是怎么通过一个按钮来实现history的来会转换呢
const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
return (
<li>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
我们通过上面的函数来渲染button以及悔棋(map有点疑问),但是jumpto函数还没有写,jumpto函数顾名思义,是当点击desc的时候回到对应的状态并且创建一个新按钮,虽然我们将各个状态都可以放在history数组中,但是怎么才能确定你回到的状态和你想要的状态是一致的呢,这里我们想一下上一篇文章中的判断输赢的方法,每一个square都有一个对应的数组下标我们通过那个下标与原来就已经放好的winnner数组中的每一个小数组去配对,在这里,我们实际上可以认为那个下标是用来标识我们每一个square状态的一个key(键)也就是说,我们是通过key来找到的状态,这里也是同样的道理,每一个历史状态也需要找到一个key形成一个map(映射)这样我们就能够准确的找到,在这里我们选move来当作每一个历史状态的key,move表示的是已经下过的步数,这样我们记下这个步数然后去从history中取出一个相应步数的片段
handleClick(i) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
const current = history[history.length - 1];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? "X" : "O";
this.setState({
history: history.concat([
{
squares: squares
}
]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext
});
}
加入跳转到某一个状态后继续下棋,那么状态的走向将会沿着现在的状态走下去,之前这个状态往后的状态将会被删掉
这里的stepNumber就是上面提到的move
render() {
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => {
const desc = move ?
'Go to move #' + move :
'Go to game start';
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
最后jumpto函数:
jumpTo(step) {
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0,
});
}
重新设置stepnumber与xisNext的值然后交给渲染函数去处理界面
小结:本文又强调了状态提升的用法,其中有一些细节性的东西需要注意:
1当在一个原来已经是一个状态提升的组件中继续状态提升的时候,需要将这个组件的状态以及管理权(事件处理)全部删除以及修改
2当涉及到处理某个动态数据时,最好给每一个动态数据都加上一个key便于查找