• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

React官网的井字棋游戏

武飞扬头像
努力学习代码的凇凇
帮助1

React官网的井字棋游戏

这个是我在官网再次复习这个小游戏时梳理的一些思路,其中也包含了我在尝试时出的一些bug

1.整体分析项目

先对整个项目进行分析,可以分割为哪些组件,组件之间应该如何联系

这个小游戏中一共分为三个组件

Square组件:用来渲染每一个小方格

Board组件:控制九个方格这一面板

Game组件:控制更宏观的东西

可以通过初步的分析设计出这三个组件,以及确定好组件之间的关系

可以先由此写出一个没有交互,没有变化的静态页面

2.为变量添加state并绑定点击事件

接下来为这个项目添加"活力"

1.Square组件需要实时打印’X’、'Y’或空白,是变化量,因此要设置为state可以修改

2.但是在这个游戏中,需要判断输赢,单独的九个Square无法关联,因此需要状态提升

将这个value设置值为Board的State,再通过参数Props传入Square,就能实现在Board层面,宏观控制九个值的关系,判断输赢

  constructor(props){
      super(props);
      this.state={
        squares: Array(9).fill(null),
      }
    }

在Board组件中声明

3.根据用户的点击,要在棋盘上落子,因此要响应onClick事件

但是,onClick是直接与Square的botton相关联,而state在Board中

直接的onClick处理函数无法越级更改父级中的State

因此,onClick事件的处理一定要在Board组件中

所以,可以在Square中额外添加一个onClick属性,由父组件的props传入,这样在点击时就会调用Board里的处理函数

学新通

value的值也与onClick一样由Board传入

相当于这个点击事件的状态也随之提升了

Square组件也被称为受控组件,完全由Board进行控制

4.下面需要完善Board里的点击事件处理函数

    handleClick(i){
      const squares =this.state.squares.slice();
      squares[i]='X';
      this.setState({
        squares: squares,
      })
    }

需要注意的是,在这里,我们用新的一份数据来替换旧的数据,这样比直接修改数据有一些好处

如果直接进行修改的话,很难追踪到数据的改变,而用整体替换,相当于有很多的历史版本,对于数据维护,或者是撤销等复杂功能提供了更好的实现方法。

写完之后点击会在相应的方格出现’X’

但是我在这里犯了两个小错误:

学新通

在这里,应该传递的是数字这个索引,而不是直接传递

如果传递数字的话,它就像一个Id,有很高的复用性,但是值得复用性就很低

因此要把8也改为this.renderSquare(8) (前几个我已经改回来了)

然后运行后点击还是没有反应,对比代码后发现,传入的onClick必须加括号

学新通

可能因为onClick的值是一段js代码,所以被理解为了函数

修改完之后就可以正常运行了

5.由于Square只有一个render方法,可以改写成函数组件,更方便一些

像这样:

  function Square(props){
    return(
      <button className="square" onClick={props.onClick()}>
          {props.value}
        </button>
      );
  }

但是之后又又又报错了,,找了半天才发现改为函数组件后,要去掉后面的括号
学新通

这样才是对的

3.轮流落子

在这里需要在state中额外添加一个变量来储存,下一位应该是哪个玩家/改玩家能不能落子

学新通
学新通

这个地方要写圆括号,写大括号会报错

4.判断输赢

要写一个能判断输赢的全局函数

  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 squares[a];
      }
    }
    return null;
  }
学新通

要防止用户多次点击,或者在决出胜者之后,不能再落子
学新通

一个类组件一般包含几部分

学新通

第一部分一般写构造函数、事件处理函数等等

第二部分一般写一些逻辑代码

第三步部分return内的,代表想在页面上展示的

5.时间旅行

实现时间回溯的功能,与之前的squares.slice()处理有很大的关系

用这种切片的处理,让数据追踪变得容易,每一次都是静态不变的历史数据版本,只要再声明一个history将其储存下来即可

我们想用最外层的game组件实现这一部分,因此history应声明在history的state中,同时要将board组件里的squares状态再次梯提升,这样history就能直接与其操作了

注意大括号

学新通

用Array声明相当于执行了js代码,所以要套在大括号里
学新通

修改后的点击事件处理函数

6.展示历史步骤

在处理未知个数据时,可以用map方法,把它们一个个都渲染出来

学新通

写成move元素的形式,然后再在最下面调用即可

比直接写在下面要好得多

在构建动态列表时,要指定一个合适的key

系统会根据key查找上次与其相同的元素,进行比较,然后重新渲染

最后引入一个state参数,stepNumber,来记录版本号,这样处理会更方便一些

两个核心函数:
学新通

所有代码:

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



  function Square(props){
    return(
      <button className="square" onClick={props.onClick}>
          {props.value}
        </button>
      );
  }
  

  class Board extends React.Component {
    

    renderSquare(i) {
      return (<Square
       value={this.props.squares[i]}
       onClick={()=>this.props.onClick(i)}
      /> )
    }
  
    render() {
      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>
      );
    }
  }
  
  class Game extends React.Component {
    constructor(props){
      super(props);
      this.state={
        history: [
          {squares: Array(9).fill(null)},
        ],
        xIsNext: true,
        stepNumber: 0,
      }
    }


    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(squares[i]||calculateWinner(squares)){
        return;
      }

      squares[i]=this.state.xIsNext?'X':'O';
      this.setState({
        history: history.concat([{
          squares: squares,
        }]),
        xIsNext: !this.state.xIsNext,
        stepNumber: history.length,
      });
    }


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

    render() {
      const history=this.state.history;
      const current=history[this.state.stepNumber];
      const winner=calculateWinner(current.squares);

      const moves=history.map((step,move)=>{
        const desc=move?'Go to move #' move:'Go to game start';
        return(
          <li key={move}>
            <botton onClick={()=>this.jumpTo(move)}>{desc}</botton>
          </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>
      );
    }
  }
  
  // ========================================
  
  const root = ReactDOM.createRoot(document.getElementById("root"));
  root.render(<Game />);
  
  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 squares[a];
      }
    }
    return null;
  }
学新通

效果:
学新通

7.总结

在学习技术的时候,永远是要勤写,光停留在理论层面是学不会的,在自己敲代码的时候,能留意到更多的细节,也会引发更多的思考,同时也会加深对这个项目的理解,也能积累下来很多代码的经验

在写代码的过程中要仔细,一个小小的错误可能会为找bug带来巨大的苦恼,所以尽量不犯小错误,能减少bug的出现,保护头发

在IT行业,一方面技术更新迭代很快,另一方面确实感受到知识面很广,要学很多的东西,了解很多的东西,还是要多学多看多了解,慢慢积累

在前端项目中,尤其是大型项目,一般要使用React Axios,

简单的项目一般使用jQuary $.ajax就可以了

当然前端也有使用很广泛的Vue框架,这个等之后有空再学

复习到这里,基本上也算复习完了,在暑期实训的同时,要把axios学了,再学一学与后端对接的知识

加油!!

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfifbca
系列文章
更多 icon
同类精品
更多 icon
继续加载