react入门学习:井字棋游戏(官方文档教程)

react入门学习:井字棋游戏

学习教程来自官方文档:https://react.docschina.org/tutorial/tutorial.html#what-are-we-building

文章目录

  • react入门学习:井字棋游戏
    • 一.创建项目
        • 遇到的问题
        • npx create-react-app my-app 太慢
    • 二.概览
      • 1.初始代码
      • 2.render方法
      • 3.Props传递数据
        • 修改代码,将数据从Board组件传递到Square组件
      • 4.给组件添加交互
        • 给Square组件实现“记忆”功能
    • 三.游戏完善
      • 1.状态提升
      • 2.函数式组件
      • 3.轮流落子
      • 4.判断胜出者
      • 5.目前代码
    • 四.历史记录功能
      • 1.提升状态
      • 2.展现历史步骤记录
      • 3.最终代码

一.创建项目

  • 1st:

    npx creact-react-app my-app
    

    小tips:npm与npx的区别

  • 把源代码删掉

    cd src
    del *
    

    windows命令行使用del *

  • 在src目录下新建文件

    touch index.css
    touch index.js
    
  • 在 index.js写入:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    
  • 运行网页

    cd my-app
    npm start
    

    此时应该能看到空白网页

    react入门学习:井字棋游戏(官方文档教程)_第1张图片

遇到的问题

npx create-react-app my-app 太慢

  • npm查看源地址以及更换源地址

  • 解决npx create-react-app速度过慢的问题

二.概览

1.初始代码

  • html文件就是自动生成的public路径下的index.html

    DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="theme-color" content="#000000" />
        <meta
          name="description"
          content="Web site created using create-react-app"
        />
        <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
        
        <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
        
        <title>React Apptitle>
      head>
      <body>
        <noscript>You need to enable JavaScript to run this app.noscript>
        <div id="root">div>
        
      body>
    html>
    
    
  • CSS文件 src路径下的index.css

    body {
      font: 14px "Century Gothic", Futura, sans-serif;
      margin: 20px;
    }
    
    ol, ul {
      padding-left: 30px;
    }
    
    .board-row:after {
      clear: both;
      content: "";
      display: table;
    }
    
    .status {
      margin-bottom: 10px;
    }
    
    .square {
      background: #fff;
      border: 1px solid #999;
      float: left;
      font-size: 24px;
      font-weight: bold;
      line-height: 34px;
      height: 34px;
      margin-right: -1px;
      margin-top: -1px;
      padding: 0;
      text-align: center;
      width: 34px;
    }
    
    .square:focus {
      outline: none;
    }
    
    .kbd-navigation .square:focus {
      background: #ddd;
    }
    
    .game {
      display: flex;
      flex-direction: row;
    }
    
    .game-info {
      margin-left: 20px;
    }
    
    
  • js文件 src路径下的index.js

    import React form 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    
    class Square extends React.Component {
        render() {
            return (
            	
    		);
            //return后面的内容加括号,防止VScode等编辑器自动在return后面加分号
        }
    }
    
    class Board extends React.Component {
        
        renderSquare(i){
            return ;
        }
        
        render() {
            const status = 'Next player:X';
            
            return (
            	
    {status}
    {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)}
    {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)}
    {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)}
    ) } } class Game extends React.Component { render() { return (
    { }
      { }
    ) } } ReactDOM.render( , document.getElementById('root') )
  • 初始代码中有三个React组件

    • Square
      • 渲染了单独的
    • Board
      • 渲染了9个方块
    • Game
      • 渲染了含有默认值的一个棋盘

2.render方法

  • React根据描述把结果展示出来

  • render方法的返回了一个React元素

  • 上面的代码使用了JSX语法糖

3.Props传递数据

修改代码,将数据从Board组件传递到Square组件

  • Board组件的renderSquare方法中,改写代码,将名为value的prop传递到Square

    renderSquare(i) {
    	return <Square value={i} />;
    }
    
  • 然后,修改Square组件的render方法,在button中加入内容

    class Square extends React.Component {
    	render() {
    		return(
    			
    		);
    	}
    }
    
  • 修改后,在my-app路径下打开终端使用npm start启动项目

    在渲染结果中,可以看到每个方格中都有数字

    react入门学习:井字棋游戏(官方文档教程)_第2张图片

  • 刚刚修改代码的过程中,把一个prop从父组件Board传递给了子组件Square

    • 在 React 应用中,数据通过 props 的传递,从父组件流向子组件

4.给组件添加交互

  • 这一步的目标是让棋盘的在点击之后每个格子能落下”X"作为棋子

  • 首先修改Square组件的render()方法的返回值中的button标签

    增加onClick属性

    class Square extends React.Component {
        render() {
            return (
                
            );
        }   
    }
    

    箭头函数也可以写为

    onClick={function(){alert('click'); }}

    但是箭头函数可以减少代码量

  • 效果

    react入门学习:井字棋游戏(官方文档教程)_第3张图片

给Square组件实现“记忆”功能

React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。

React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。

  • 通过state来实现“记忆”功能

  • 在React组件的构造函数中设置this.state初始化state

    this.state应被视为一个组件的私有属性。在 this.state 中存储当前每个方格(Square)的值,并且在每次方格被点击的时候改变这个值。

  • 首先,在Square中用构造函数来初始化state

    class Square extends React.Component {
    	constructor(props){
    		super(props);
    		this.state = {
    			value : null.
    		};
    	}
    	
    	render() {
    		return (
    			
    		);
    	}
    }
    

    在所有含有构造函数的的 React 组件中,构造函数必须以 super(props) 开头

  • 其次,修改Square组件的render方法,实现每当方格被点击时显示当前state值

    方法是在Square组件的render方法中的onClick事件监听函数中调用this.setState

    class Square extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          value: null,
        };
      }
    
      render() {
        return (
          
        );
      }
    }
    

    这样子就能实现:在每次

三.游戏完善

井字棋游戏 要放置 "X"和"O"两种棋

1.状态提升

react入门学习:井字棋游戏(官方文档教程)_第4张图片

  • 为Board组件添加构造函数,将Board组件的初始状态设置为长度为9的空数组值

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

    考虑到填充棋盘后,每个格子上可能的形式是null,O,X三种

    这些我们打算存储到squares数组里面,那么就要修改BoardrenderSquare方法来读取这些值

        renderSquare(i) {
            return ;
        }
    

    这样,每个Square就都能接收到一个 value prop 了,这个 prop 的值可以是 'X''O'、 或 nullnull 代表空方格)。

  • 接着,要修改Square的事件监听函数

    react入门学习:井字棋游戏(官方文档教程)_第5张图片

     renderSquare(i) {
            return ( this.handleClick(i)}
            />);
        }
      
    
  • 再其次,从Board组件向Square组件中传递valueonClick两个props参数

    需要修改Square的代码

    react入门学习:井字棋游戏(官方文档教程)_第6张图片

    class Square extends React.Component {
    	render(){
    		return (
    			
    		);
    	}
    }
    
  • 当每一个Square被点击时,Board提供的onClick函数就会触发,这是如何实现的呢?

    react入门学习:井字棋游戏(官方文档教程)_第7张图片

  • 于是,添加handleClick方法

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

    react入门学习:井字棋游戏(官方文档教程)_第8张图片

2.函数式组件

  • 定义一个函数,接收props参数,然后返回需要渲染的元素

  • 把Square组件重写为一个函数组件

    function(props)
    {
    	return(
    	
    	
    	);
    }
    

    可以注意到this.props被换成了props

箭头函数也变成了onClick={props.onClick}

3.轮流落子

  • 将"X"默认设置为先手棋,设置一个布尔值来表示下一步轮到哪个玩家

  • 棋子每移动一步,xIsNext都会反转,该值确定下一步轮到哪个玩家,并且游戏的状态会被保存下来

    在构造函数中添加xIsNext

    修改handleClick函数

    constructor(props) {
            super(props);
            this.state = {
                squares: Array(9).fill(null),
                xIsNext: true,
    
            };
        }
    
        handleClick(i) {
            const squares = this.state.squares.slice();
            squares[i] = this.state.xIsNext? 'X':'O';
            this.setState({
                squares: squares,
                xIsNext:!this.state.xIsNext,
            });
        }
    
  • 效果

    这样子就实现了轮流落子了

    react入门学习:井字棋游戏(官方文档教程)_第9张图片

    CSS修改了一下

    body {
      font: 14px "Century Gothic", Futura, sans-serif;
      margin: 20px;
    }
    
    ol,
    ul {
      padding-left: 30px;
    }
    
    .board-row:after {
      clear: both;
      content: "";
      display: table;
    }
    
    .status {
      margin-bottom: 10px;
    }
    
    .square {
      background: #fff;
      border: 5px solid rgb(7, 130, 168);
      float: left;
      font-size: 24px;
      font-weight: bold;
      line-height: 34px;
      height: 50px;
      margin-right: -1px;
      margin-top: -1px;
      padding: 0;
      text-align: center;
      width: 50px;
    }
    
    .square:focus {
      outline: none;
    }
    
    .kbd-navigation .square:focus {
      background: #ddd;
    }
    
    .game {
      display: flex;
      flex-direction: row;
    }
    
    .game-info {
      margin-left: 20px;
    }
    
  • 显示轮到哪个玩家

    在Board组件的render方法中修改status的值

        render() {
            const status = 'Next player:'+(this.state.xIsNext?'X':'O');
    
            return (
                
    {status}
    {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)}
    {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)}
    {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)}
    ) }

    效果:

    react入门学习:井字棋游戏(官方文档教程)_第10张图片

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;
}

代码中const[a,b,c]=lines[i]

相当于

const a = lines[i][0];
const b = lines[i][1];
const c = lines[i][2];
  • 参考

    • JavaScript——编程风格
    • ES6 解构赋值

    react入门学习:井字棋游戏(官方文档教程)_第11张图片

  • 接着,在Board组件的render方法中调用刚刚那个函数检查是否有玩家胜出,有人胜出就把玩家信息显示出来

    修改Board组件的render方法

        render() {
            const winner = calculateWinner(this.state.squares);
            let status;
            if (winner) {
                status = 'Winner:' + winner;
    
            } else {
                status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
            }
            
            return (
                
    {status}
    {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)}
    {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)}
    {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)}
    ) }
  • 效果

    react入门学习:井字棋游戏(官方文档教程)_第12张图片

  • 此时还有个问题

    已经有人胜出了,但是棋盘还能落子

    还有个bug,当某个Square落子之后,还能覆盖继续落子

    修改handleClick,使得当有玩家胜出时,或者某个Square被填充时,该函数不做任何处理直接返回

       handleClick(i) {
            const squares = this.state.squares.slice();
            if (calculateWinner(squares) || squares[i]) {
                return;
            }
            squares[i] = this.state.xIsNext? 'X':'O';
            this.setState({
                squares: squares,
                xIsNext:!this.state.xIsNext,
            });
        }
    

5.目前代码

  • 此时已经实现了井字棋游戏的功能

  • 目前的index.js代码如下

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    
    // class Square extends React.Component {
    // 	render(){
    // 		return (
    // 			
    // 		);
    // 	}
    // }
    function Square(props) {
    
        return (
            
        );
    }
    class Board extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                squares: Array(9).fill(null),
                xIsNext: true,
    
            };
        }
    
        handleClick(i) {
            const squares = this.state.squares.slice();
            if (calculateWinner(squares) || squares[i]) {
                return;
            }
            squares[i] = this.state.xIsNext? 'X':'O';
            this.setState({
                squares: squares,
                xIsNext:!this.state.xIsNext,
            });
        }
        renderSquare(i) {
            return ( this.handleClick(i)}
            />);
        }
    
        render() {
            const winner = calculateWinner(this.state.squares);
            let status;
            if (winner) {
                status = 'Winner:' + winner;
    
            } else {
                status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
            }
            
            return (
                
    {status}
    {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)}
    {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)}
    {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)}
    ) } } class Game extends React.Component { render() { return (
    { }
      { }
    ) } } ReactDOM.render( , 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 squares[a]; } } return null; }

四.历史记录功能

react入门学习:井字棋游戏(官方文档教程)_第13张图片

react入门学习:井字棋游戏(官方文档教程)_第14张图片

1.提升状态

  • 目标是实现顶层Game组件展示出一个历史步骤的列表

    这个功能需要访问history的数据

    故把history这个state放在顶层Game组件中

    react入门学习:井字棋游戏(官方文档教程)_第15张图片

  • 首先,在Game组件的构造函数中初始化state

    constructor(props) {
    	super(props);
    	this.state = {
    		history:[{
    			squares: Array(9).fill(null),
    		}],
    		xIsNext: true,
    	};
    }
    
  • 接着

    react入门学习:井字棋游戏(官方文档教程)_第16张图片

    • 修改后的Board组件的代码:

      class Board extends React.Component {
      
          handleClick(i) {
              const squares = this.state.squares.slice();
              if (calculateWinner(squares) || squares[i]) {
                  return;
              }
              squares[i] = this.state.xIsNext? 'X':'O';
              this.setState({
                  squares: squares,
                  xIsNext:!this.state.xIsNext,
              });
          }
          renderSquare(i) {
              return ( this.props.onClick(i)}
              />);
          }
      
          render() {
              const winner = calculateWinner(this.state.squares);
              let status;
              if (winner) {
                  status = 'Winner:' + winner;
      
              } else {
                  status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
              }
              
              return (
                  
      {status}
      {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)}
      {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)}
      {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)}
      ) } }
  • 接着,修改Game组件的render函数,用最新的一次历史记录来确定并展示游戏的状态

    class Game extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                history: [{
                    squares: Array(9).fill(null),
                }],
                xIsNext: true,
            };
        }
        render() {
            const history = this.state.history;
            const current = history[history.length - 1];
            const winner = calculateWinner(current.squares);
            let status;
            if (winner) {
                status = 'Winner: ' + winner;
    
            } else {
                status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
            }
            return (
                
    this.handleClick(i)} />
    {status}
      { }
    ) } }
    • 因为Game组件渲染了游戏状态,所以可以把Board组件的render方法中的对应代码移除,修改Board组件如下

      class Board extends React.Component {
        renderSquare(i) {
          return (
             this.props.onClick(i)}
            />
          );
        }
      
        render() {
          return (
            
      {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)}
      {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)}
      {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)}
      ); } }
    • 还要把Board组件的handleClick方法移动到Game组件中

          handleClick(i) {
              const history = this.state.history
              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,
                  }]),
                  xIsNext:!this.state.xIsNext,
              })
          }
      

2.展现历史步骤记录

  • 可以把历史记录以历史步骤列表的形式展现给玩家

  • 可以通过map方法,把历史步骤映射为代表按钮的React元素

    • 然后展示出一个按钮的列表
    • 点击这些按钮,可以跳转到对应的历史步骤
  • 在Game组件的render方法中调用history的map方法

              const moves = history.map((step, move) => {
                  const desc = move ?
                      'Go to move #' + move :
                      'Go to game start';
                  return (
                      
  • ) })
  • 对于井字棋历史记录的每一步,都创建出了一个包含按钮

  • )
  • 在Game的构造函数中添加stepNumber这个值来表示我们当前正在查看哪一项历史记录

    constructor(props) {
            super(props);
            this.state = {
                history: [{
                    squares: Array(9).fill(null),
                }],
                stepNumber: 0,
                xIsNext: true,
            };
        }
    
  • 添加jumpTo方法

    
        jumpTo(step) {
            this.setState({
                stepNumber: step,
                xIsNext:(step%2)===0,
            })
        }
    
  • 修改Game组件的handleClick方法

    react入门学习:井字棋游戏(官方文档教程)_第18张图片

        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,
                }]),
                stepNumber: history.length,
                xIsNext:!this.state.xIsNext,
            })
        }
    
  • 再修改Game组件的render方法,将代码改为根据当前stepNumber渲染

    const current = history[this.state.stepNumber];
    
  • 3.最终代码

    • index.js

      import React from 'react';
      import ReactDOM from 'react-dom';
      import './index.css';
      
      // class Square extends React.Component {
      // 	render(){
      // 		return (
      // 			
      // 		);
      // 	}
      // }
      function Square(props) {
      
          return (
              
          );
      }
      class Board extends React.Component {
        renderSquare(i) {
          return (
             this.props.onClick(i)}
            />
          );
        }
      
        render() {
          return (
            
      {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)}
      {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)}
      {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)}
      ); } } class Game extends React.Component { constructor(props) { super(props); this.state = { history: [{ squares: Array(9).fill(null), }], stepNumber: 0, xIsNext: 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, }]), stepNumber: history.length, xIsNext:!this.state.xIsNext, }) } 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 (
    • ) }) let status; if (winner) { status = 'Winner: ' + winner; } else { status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); } return (
      this.handleClick(i)} />
      {status}
        {moves}
      ); } } ReactDOM.render( , 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 squares[a]; } } return null; }
    • 效果

      react入门学习:井字棋游戏(官方文档教程)_第19张图片

    你可能感兴趣的:(前端,react.js,游戏,javascript)