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]

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