React井字棋

井字棋

React官方文档示例WebApp

三个组件

  • Square:棋盘的一小格
  • Board:棋盘
  • Game:游戏信息的显示和过程的监控

其中Square、Board是展示组件不负责数据交互,Game是容器组件,负责数据交互。

实现功能

  1. 轮流下子
  2. 判断胜负
  3. 历史纪录
  4. 历史纪录升降序转换
  5. 高亮显示获胜行

代码

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';



function Square(props) {
  if (props.highlight) {
    return (
      <button className="square" onClick={() => props.onClick()} style={{color: "red"}}> 
        {props.value}
      </button>
    );
  }else {
  return (
    <button className="square" onClick={() => props.onClick()}>
      {props.value}
    </button>
  );
  }
}

Square组件:对其属性highligt进行判断,来决定颜色是否为红色。按钮的值是他的value属性。同时还有一个onclick方法继承自父组件。

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

  render() {
      var rows = [];
      for (var i = 0; i < 3; i++) {
          var row = [];
          for (var j = 3 * i; j < 3 * i + 3; j++) {
              row.push(this.renderSquare(j));
          }
          rows.push(<div className='board-row'>{row}</div>)
      }
    return (
      <div>
        {rows}
      </div>
    );
  }
}

Board组件:使用renderSquare(i)方法来创建Square组件,其value值是Board组件square[i],onclick是继承自Board组件的方法,highlight是判断Board组件的line中是否有i,render方法中用两个for循环来渲染出九宫格。

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null),
          lastPos:'Get Game Start'
        }
      ],
      stepNumber: 0,
      xIsNext: true,
      sort: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();
    const posX = Math.ceil((i+1) / 3);
    const posY = (i + 1) % 3 ? (i + 1) % 3 : 3;
    const position = '(' + posX + ', ' + posY + ')';
    if (calculateWinner(squares).winner || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? "X" : "O";
    this.setState({
      history: history.concat([
        {
          squares: squares,
          lastPos:squares[i] + ' To ' + position
        }
      ]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext,
    });
  }

  jumpTo(step) {
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0
    });
  }

   toggleSort() {
    this.setState({
      sort:!this.state.sort,
    })
  }

  render() {
    let history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares).winner;
    const line = calculateWinner(current.squares).line;
    if (!this.state.sort){
        history = this.state.history.slice();
          history.reverse();
      }
    const moves = history.map((step, move) => {
      const desc = step.lastPos;
      if (this.state.stepNumber == move) {
          return (
        <li key={move}>
          <button onClick={() => this.jumpTo(move)}><strong>{desc}</strong></button>
        </li>
        );
      } else {
          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)}
            line={line}
          />
        </div>
        <div className="game-info">
         <button onClick={() => this.toggleSort()}>Sort</button>
          <div>{status}</div>
          <ol>{moves}</ol>
        </div>
      </div>
    );
  }
}

Game组件:首先看他的状态(state),一个是history数组,其中每个元素是一个对象,对象里有一个记录着棋盘形态的数组和这一步走的位置;stepNumber,记录走的步数;xIsNext判断轮到谁下子;sort判断升降序。
handleClick()方法:是最终传入Square的方法,它获得了历史纪录和这步没走之前棋局,如果已经获胜或这格已经落子,就直接返回,没有的话就计算他的位置并更新history,stepNumber,和xIsNext
render()中获取历史纪录和当前的棋局,计算胜者和获胜的棋子,判断是否需要升降序,moves对数组history进行map,如果stepNumber与索引相等,就返回加粗显示的列表,否则返回普通列表(这里注意列表项要有key值)。jumpTo()方法接受步数,并将stepNumber设置为参数,并更新xIsNext。下面通过status来显示游戏状态,最终渲染Board组件,升降序按钮,状态显示,历史记录列表。

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

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]) {
      return {winner:squares[a], line:[a, b, c]};
    }
  }
  return {winner:null, line:[]};
}

总结

初次使用React开发,感觉难度是有的,一些细节了解的也不是很清楚,但是组件化开发显而易见的好处就是思路清晰,各组件联系紧密又各司其职。代码逻辑性很强也有利于后期维护和功能的升级。