我终于对React下手了!

532 阅读3分钟

文档概念看得迷迷糊糊,转身就忘,还是自己敲吧,跟着官方文档来一遍棋盘练手。

import React from 'react';
import ReactDOM, { createPortal } from 'react-dom';
import './index.css';
// 5.9:状态提升(子组件不再单独保留state状态,而是提升到公共的父组件进行统一维护和修改)
// 5.9:函数式组件(没有state,只有render函数,写法比类组件简单),不需要写render,直接return;没有this,直接props;没有生命周期
// 5.9:双方轮流落子,定义布尔值变量控制切换并更新布尔值
// 5.9:点击已填充的仍会切换,进行if判断只在未填充时进行修改 if(xxx) return
// 5.9:判断胜者,遍历成功的数组
// 5.10:状态提升 board保存state提升到game组件 stepNumber控制步骤
// 5.10:时间旅行
// 5.12:游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号) 程序员都是从0开始计数的! 列号=索引%总列数 行号=parseInt(索引/总列数)
// 5.13:添加一个可以升序或降序显示历史记录的按钮,新增布尔值绑定ol标签的reverse属性列表的条目是否倒序 切换顺序 temphistory 注意不再根据index判断 moves中desc的渲染 step存储每步序号 注意reverse直接改变原数组
// 5:13:每当有人获胜时,高亮显示连成一线的 3 颗棋子。 props传递winnerline
// 5.13:当无人获胜时,显示一个平局的消息 点击时stepNumber为9
// // Square类组件
// class Square extends React.Component {
//   render () {
//     // console.log(this) // Square实例
//     return (
//       <button className="square" onClick={this.props.onClick}>
//         {this.props.value}
//       </button>
//     );
//   }
// }
// Square函数式组件
function Square (props) {
  return (
    <button className={`square ${props.isWinner ? 'winner ' : ''}`} onClick={props.onClick}>
      {props.value}
    </button>
  )
}

class Board extends React.Component {
  renderSquare (i) {
    return <Square key={i} value={this.props.squareArr[i]} isWinner={this.props.winnerLine && this.props.winnerLine.includes(i)} onClick={() => this.props.onClick(i)} />;
  }

  render () {
    const finalArr = [
      [0, 1, 2],
      [3, 4, 5],
      [6, 7, 8]
    ]
    return (
      <div>
        {
          finalArr.map((outter, index) => {
            return (
              <div key={index} className="board-row">
                {
                  outter.map(item => { return this.renderSquare(item) })
                }
              </div>
            )
          })
        }
      </div>
    );
  }
}

class Game extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      history: [
        {
          squareArr: Array(9).fill(null),
          currentXY: null,
          step: 0
        }
      ],
      XIsNext: true,
      stepNumber: 0,
      isReversed: false
    }
  }
  render () {
    let history = this.state.history
    const current = history[this.state.stepNumber]
    // debugger
    const { winner, winnerLine } = this.caculateWinner(current.squareArr)
    const status = winner ? 'Winner' + winner : 'Next player: ' + (this.state.XIsNext ? 'X' : 'O')
    const tempHistory = history.slice()
    if (this.state.isReversed) {
      tempHistory.reverse()
    }
    const moves = tempHistory.map((item, index) => {
      var desc = item.currentXY || item.currentXY === 0 ? `Go To Move #${item.step} Col: ${parseInt(item.currentXY % 3)} Row: ${parseInt(item.currentXY / 3)}` : 'Go To game start'
      return (
        <li key={item.currentXY}> <button onClick={() => this.jumpTo(item.step)} className={item.step === this.state.stepNumber ? 'active' : null} >{desc}</button></li>
      )
    })
    // 渲染不对肯定是board接收的数据不对 debug这么费劲吗 不动脑子 自己气自己
    return (
      <div className="game">
        <div className="game-board">
          <Board squareArr={current.squareArr} winnerLine={winnerLine} onClick={(i) => this.handleClick(i)} />
        </div>
        <div className="game-info">
          <button onClick={() => { this.handleToggleSort() }}>{this.state.isReversed ? '升序' : '降序'}</button>
          <div>{status}</div>
          <ol reversed={this.state.isReversed}>{moves}</ol>
        </div>
      </div>
    );
  }
  handleClick (i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1)
    const current = history[history.length - 1]
    let squareArr = current.squareArr.slice()
    if (this.caculateWinner(squareArr).winner || squareArr[i] && this.state.stepNumber < 9) {
      return
    }
    squareArr[i] = this.state.XIsNext ? 'X' : 'O'
    this.setState({
      history: history.concat({ squareArr: squareArr, currentXY: i, step: this.state.stepNumber + 1 }),
      XIsNext: !this.state.XIsNext,
      stepNumber: history.length
    }, () => {
      setTimeout(() => {
        if (!this.caculateWinner(squareArr).winner && this.state.stepNumber >= 9) {
          console.log(this.state.stepNumber)
          alert('平局')
        }
      }, 0)
    })
    // TODO:这种会先弹出对话框再更新页面 不是想要的结果 应该先更新页面再弹出对话框 有没有除上一种之外的方案呢?
    // this.setState({
    //   history: history.concat({ squareArr: squareArr, currentXY: i, step: this.state.stepNumber + 1 }),
    //   XIsNext: !this.state.XIsNext,
    //   stepNumber: history.length
    // }, () => {
    //   if (!this.caculateWinner(squareArr).winner && this.state.stepNumber >= 9) {
    //     console.log(this.state.stepNumber)
    //     alert('平局')
    //   }
    // })
  }
  caculateWinner (squareArr) {
    let winnerArr = [
      [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 < winnerArr.length; i++) {
      let [a, b, c] = winnerArr[i]
      if (squareArr[a] && squareArr[a] === squareArr[b] && squareArr[a] === squareArr[c]) {
        return { winner: squareArr[a], winnerLine: winnerArr[i] }
      }
    }
    return { winner: null }
  }
  jumpTo (index) {
    let history = this.state.history
    this.setState({
      stepNumber: index,
      XIsNext: index % 2 === 0 ? 'O' : 'X'
      // 不在这里set history在点击的棋盘的时候才进行更新 如果在这里更新history那么moves也会更新 就不能任意切换了
    })
  }
  handleToggleSort () {
    this.setState({
      isReversed: !this.state.isReversed
    })
  }
}

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

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