iT邦幫忙

1

React 學習筆記_15(React 圈圈叉叉練習 - 2)

前言

  • 上一篇已經完成了圈圈叉叉的遊戲,現在要加入"回到上一步"的功能。
  • 上一篇的程式中使用了slice()這個方法來複製squares,其目的就是在於可以記錄每一次squares的變化,不會直接改變到原始的資料,為了達到"回到上一步"這個功能,需要將上一步的squares Array儲存起來。

Step 1 : 再次提升State

我們希望最頂層的Component(Game)能夠紀錄所有squares的變化,所以把State提升到最頂層。

Game :
1.提升State到本層,並建立History來存放所有squares的變化。

class Game extends React.Component
{
    //Step 1:提升State到本層
    constructor(props)
    {
        super(props)
        this.state = {
            history: [{
                squares: Array(9).fill(""),
              }],
              xIsNext: true
        }
    }

    render()
    {
        return(
            <div className="game">
            <div className="game-board">
                <Board />
            </div>
        </div>
        )
    }
}

Board :
1.移除本層的state。
2.移除本層的handleClick funciton。
3.將this.state更改為this.props(本層state改為父層傳遞)。

class Board extends React.Component
{
    //Step 1:移除本層的state。
    
    //Step 2:移除本層的handleClick funciton。
    
    renderSquare(i) {
        //Step 3:將this.state更改為this.props
        return <Square value={this.props.squares[i]} onClick={() => this.props.handleClick(i)}/>;
    }

    render()
    {
        const Winner = calculateWinner(this.state.squares);
        let status;

        if(Winner)
        {
            status = "Winner :" + Winner;
        }
        else
        {
            status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
        }

        return(
            <div>
                <div className="status">{status}</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>
                <dic>
                    <button onClick={this.Reset}>ReStart</button>
                </dic>
            </div>
        )
    }
}

Step 2 : 在Game中新增上一步的動作並顯示遊戲狀態

Game :
1.建立上一步動作。
2.顯示遊戲狀態。

class Game extends React.Component
{
    constructor(props)
    {
        super(props)
        this.state = {
            history: [{
                squares: Array(9).fill(""),
              }],
              xIsNext: true
        }
    }

    render()
    {
        const history = this.state.history;
        const current = history[history.length - 1]; //Step 1:建立上一步動作
        const winner = calculateWinner(current.squares); //Step 2:顯示遊戲狀態
        let status;

        if(winner)
        {
            status = 'Winner: ' + winner;
        }
        else
        {
            status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
        }

        return(
            <div className="game">
            <div className="game-board">
                <Board />
            </div>
        </div>
        )
    }
}

Board : 在Game中已經顯示了遊戲的狀態,便可以將本層的顯示遊戲的狀態功能移除
1.移除顯示遊戲的狀態功能。

class Board extends React.Component
{
    renderSquare(i) {
        return <Square value={this.props.squares[i]} onClick={() => this.props.handleClick(i)}/>;
    }

    render()
    {
        //Step 1:移除顯示遊戲的狀態功能
        
        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>
        )
    }
}

Step 3 : 移動handleClick function

Game :
1.將handleClick function從 Board component 移到 Game component
2.將squares與handleClick function透過props傳遞給子層

class Game extends React.Component
{
    constructor(props)
    {
        super(props)
        this.state = {
            history: [{
                squares: Array(9).fill(""),
              }],
              xIsNext: true
        }
    }

    //Step 1:新增handleClick function
    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,
        });
    };

    render()
    {
        const history = this.state.history;
        const current = history[history.length - 1]; 
        const winner = calculateWinner(current.squares); 
        let status;

        if(winner)
        {
            status = 'Winner: ' + winner;
        }
        else
        {
            status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
        }

        return(
            <div className="game">
            <div className="game-board">
                //Step 2:將squares與handleClick function透過props傳遞給子層
                <Board 
                squares={current.squares}
                onClick={(i) => {this.handleClick(i)}}
                />
            </div>
        </div>
        )
    }
}

Step 4 : 展示過去的動作

Game :
1.新增一個function來判斷是否有下一步動作(以改變button的文字)並選染到一個button中。
2.新增按鈕被點擊後所觸發的funciton(JumpTo)。
3.修改render中選取最後一個動作,根據stepNumber來選取動作。

class Game extends React.Component
{
    constructor(props)
    {
        super(props)
        this.state = {
            history: [{
                squares: Array(9).fill(""),
              }],
              //Step 2-1:在State中新增一個stepNumber來計算步數。
              stepNumber: 0,
              xIsNext: true
        }
    }

    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,
        });
    };

    //Step 2:新增onclick function
    jumpTo = (move) => {
        this.setState({
            stepNumber : move, //Step 2-2:更新stepNumber狀態
            //Step 2-3:檢測目前步數是否為偶數(判斷下一步為"X"(偶數)還是"O"(基數))
            xIsNext : (move % 2) === 0 
        });
    };

    render()
    {
        const history = this.state.history;
        //Step 3:根據stepNumber來選取動作
        const current = history[this.state.stepNumber]; 
        const winner = calculateWinner(current.squares); 

        //Step 1:判斷是否有下一步,並將結果選染到button中
        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>
            );
          });

        let status;

        if(winner)
        {
            status = 'Winner: ' + winner;
        }
        else
        {
            status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
        }

        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>
            </div>
        </div>
        )
    }
}

參考資料 :
React學習指南


尚未有邦友留言

立即登入留言