React Tutorial-Tic Tac Toe完善

跟着learn by doing教程走下來感覺掌握得模模糊糊,之後繼續看了step-by-step guide以後感覺相關概念語法清楚多了。個人建議先看後面這部分再做這個Tic Tac Toe的小遊戲,相當於有了理性認知後再進行實踐,這樣印象比較深,step-by-step guide其實內容不多。另外網站有中文選項,一開始沒看到全英文做完了小遊戲才發現= =。中文雖然看的快但是有時候會get不到原來英文表達的意思,覺得奇怪的地方建議還是看英文。

If you have extra time or want to practice your new React skills, here are some ideas for improvements that you could make to the tic-tac-toe game which are listed in order of increasing difficulty:

  1. Display the location for each move in the format (col, row) in the move history list.
  2. Bold the currently selected item in the move list.
  3. Rewrite Board to use two loops to make the squares instead of hardcoding them.
  4. Add a toggle button that lets you sort the moves in either ascending or descending order.
  5. When someone wins, highlight the three squares that caused the win.
  6. When no one wins, display a message about the result being a draw.

 這幾個需求個人認爲不像官網說的難度遞增,相關概念掌握好了挺簡單的,現在就差個css搞得好看一點了吧hh。

React數據流總體是從上往下的,小部分是從下網上的。項目中game組件擁有最多的狀態,通過props將相關數據向下傳遞,最底層的組件是square。每當square的點擊事件發生,調用props.onClick屬性對應的方法,該方法由上層Board的this.props.onClick屬性決定,該屬性又由再上層的Game的決定,Game指定了該方法爲handleClick(i),也就是說,點擊事件最終會調用Game的handleClick方法。

該方法使用了setState方法,使得Game組件的狀態(state)發生改變,因此React會調用其render方法,重新進行渲染,並且其下級組件也會調用render方法重新進行渲染,從而使得使得UI發生改變。如果數據流清楚了我想上面的問題也解決了一半了。

完善該項目的過程中,一個比較重要的問題是如何確定是否應該添加state以及在哪裏添加state,這裏貼上官方的說法,講的非常清晰:

通過問自己以下三個問題,你可以逐個檢查相應數據是否屬於 state:

  1. 該數據是否是由父組件通過 props 傳遞而來的?如果是,那它應該不是 state。
  2. 該數據是否隨時間的推移而保持不變?如果是,那它應該也不是 state。
  3. 你能否根據其他 state 或 props 計算出該數據的值?如果是,那它也不是 state。

 確定哪個組件能夠改變這些 state,或者說擁有這些 state。對於應用中的每一個 state:

  1. 找到根據這個 state 進行渲染的所有組件。
  2. 找到他們的共同所有者(common owner)組件(在組件層級上高於所有需要該 state 的組件
  3. 該共同所有者組件或者比它層級更高的組件應該擁有該 state。

這裏有個地方寫起來有點變扭,就是第3條寫雙重循環創建嵌套元素,不知道如何用React.Fragment當中直接動態添加子節點,只能調用React.createElement方法來添加div的子元素,如果有更優雅的寫法請告訴我:)

const board=Array();
    for(let i=0;i<3;i++){
      let row=Array();
      for(let j=0;j<3;j++){
        row.push(this.renderSquare(i*3+j));
      }
      board.push(React.createElement('div',{className:'board-row',key:i},row));
    }
function Square(props) {
  const lastIndex=props.lastIndex;
  const index=props.index;
  const line=props.line;
  return (
    <button className="square" onClick={props.onClick} style={{fontWeight:(lastIndex===index?'bold':'normal'),color:(line&&line.includes(index)?'#436EEE':'')}}>
      {props.value}
    </button>
  );
}

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

  render() {
    const board=Array();
    for(let i=0;i<3;i++){
      let row=Array();
      for(let j=0;j<3;j++){
        row.push(this.renderSquare(i*3+j));
      }
      board.push(React.createElement('div',{className:'board-row',key:i},row));
    }
    return (
      <div>
        {board}
      </div>
    );
  }
}

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [//history存的是對象的列表,對象有squares屬性
        {
          squares: Array(9).fill(null),
          x:null,
          y:null
        }
      ],
      stepNumber: 0,
      xIsNext: true,
      lastIndex:0,
      isAsc:true
    };
  }
  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,
          x:i%3+1,
          y:3-Math.floor(i/3)
        }
      ]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext,
      lastIndex:i
    });
  }
  jumpTo(e,step) {
    
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0,
    });
  }
  onAscChange(){
    this.setState({
      isAsc:!this.state.isAsc
    })
  }
  
  render() {
    const history=this.state.history;
    const current=history[this.state.stepNumber];
    const result=calculateWinner(current.squares);
    const winner=result&&result[0];//短路邏輯
    const line=result&&result[1];//line可以從函數返回值中得出,不應存爲state
    const lastIndex=this.state.lastIndex;
    const isAsc=this.state.isAsc;
    const moves=history.map((step,move)=>{
      const desc=move?'Go to move #'+"("+step.x+","+step.y+")":'Go to game start';
      return (
        <li key={move}>
          <button onClick={(e)=>this.jumpTo(e,move)}>{desc}</button>
        </li>
      );
      });
    if(!isAsc) moves.reverse();
    let status;
    if(winner){
      status='Winner:'+winner;
    }
    else{
      status='Next player:'+(this.state.xIsNext?'X':'O');
      if(current.squares.filter(Boolean).length===9){
        status="It's a draw!";
      }
    }
    return (
      <div className="game">
        <div className="game-board">
          <Board 
            squares={current.squares}
            lastIndex={lastIndex}
            line={line}
            onClick={(i)=>this.handleClick(i)}
            />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <button onClick={()=>this.onAscChange()}>{isAsc?"變爲降序排列":"變爲升序排列"}</button>
          <ol>{moves}</ol>
        </div>
      </div>
    );
  }
}

// ========================================

ReactDOM.render(
  <Game />,
  document.getElementById('root')
);

function calculateWinner(squares) {
  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 (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {//三個step一樣爲勝利
      return [squares[a],lines[i]];
    }
  }
  return null;
}

有需要的到我的資源中下載,最後附上效果:)

[video-to-gif output image]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章