ReactJs官方入门教程(井字游戏)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReactJs官方入门教程(井字游戏)相关的知识,希望对你有一定的参考价值。

参考技术A

Today, we’re going to build an interactive tic-tac-toe game.

If you like, you can check out the final result here: Final Result . Don’t worry if the code doesn’t make sense to you yet, or if it uses an unfamiliar syntax. We will be learning how to build this game step by step throughout this tutorial.

Try playing the game. You can also click on a button in the move list to go “back in time” and see what the board looked like just after that move was made.

Once you get a little familiar with the game, feel free to close that tab, as we’ll start from a simpler template in the next sections.

We’ll assume some familiarity with html and javascript, but you should be able to follow along even if you haven’t used them before.

If you need a refresher on JavaScript, we recommend reading this guide . Note that we’re also using some features from ES6, a recent version of JavaScript. In this tutorial, we’re using arrow functions , classes , let , and const statements. You can use the Babel REPL to check what ES6 code compiles to.

There are two ways to complete this tutorial: you could either write the code right in the browser, or you could set up a local development environment on your machine. You can choose either option depending on what you feel comfortable with.

This is the quickest way to get started!

First, open this starter code in a new tab. It should display an empty tic-tac-toe field. We will be editing that code during this tutorial.

You can now skip the next section about setting up a local development environment and head straight to the overview .

Alternatively, you can set up a project on your computer.

Note: this is completely optional and not required for this tutorial!

This is more work, but lets you work from the comfort of your editor.

If you want to do it, here are the steps to follow:

Now if you run npm start in the project folder and open http://localhost:3000 in the browser, you should see an empty tic-tac-toe field.

We recommend following these instructions to configure syntax highlighting for your editor.

If you get stuck, check out the community support resources . In particular, Reactiflux chat is a great way to get quick help. If you don’t get a good answer anywhere, please file an issue, and we’ll help you out.

With this out of the way, let’s get started!

React is a declarative, efficient, and flexible JavaScript library for building user interfaces.

React has a few different kinds of components, but we’ll start with React.Component subclasses:

We’ll get to the funny XML-like tags in a second. Your components tell React what you want to render – then React will efficiently update and render just the right components when your data changes.

Here, ShoppingList is a React component class , or React component type . A component takes in parameters, called props , and returns a hierarchy of views to display via the render method.

The render method returns a description of what you want to render, and then React takes that description and renders it to the screen. In particular, render returns a React element , which is a lightweight description of what to render. Most React developers use a special syntax called JSX which makes it easier to write these structures. The <div /> syntax is transformed at build time to React.createElement(\'div\') . The example above is equivalent to:

See full expanded version.

If you’re curious, createElement() is described in more detail in the API reference , but we won’t be using it directly in this tutorial. Instead, we will keep using JSX.

You can put any JavaScript expression within braces inside JSX. Each React element is a real JavaScript object that you can store in a variable or pass around your program.

The ShoppingList component only renders built-in DOM components, but you can compose custom React components just as easily, by writing <ShoppingList /> . Each component is encapsulated so it can operate independently, which allows you to build complex UIs out of simple components.

Start with this example: Starter Code .

It contains the shell of what we’re building today. We’ve provided the styles so you only need to worry about the JavaScript.

In particular, we have three components:

The Square component renders a single <button> , the Board renders 9 squares, and the Game component renders a board with some placeholders that we’ll fill in later. None of the components are interactive at this point.

Just to get our feet wet, let’s try passing some data from the Board component to the Square component.

In Board’s renderSquare method, change the code to pass a value prop to the Square:

Then change Square’s render method to show that value by replacing /* TODO */ with this.props.value :

Before:

[站外图片上传中...(image-d45328-1515587905953)]

After: You should see a number in each square in the rendered output.

[站外图片上传中...(image-c8a682-1515587905953)]

View the current code.

Let’s make the Square component fill in an “X” when you click it. Try changing the button tag returned in the render() function of the Square like this:

If you click on a square now, you should get an alert in your browser.

This uses the new JavaScript arrow function syntax. Note that we’re passing a function as the onClick prop. Doing onClick=alert(\'click\') would alert immediately instead of when the button is clicked.

React components can have state by setting this.state in the constructor, which should be considered private to the component. Let’s store the current value of the square in state, and change it when the square is clicked.

First, add a constructor to the class to initialize the state:

In JavaScript classes , you need to explicitly call super(); when defining the constructor of a subclass.

Now change the Square render method to display the value from the current state, and to toggle it on click:

Now the <button> tag looks like this:

Whenever this.setState is called, an update to the component is scheduled, causing React to merge in the passed state update and rerender the component along with its descendants. When the component rerenders, this.state.value will be \'X\' so you’ll see an X in the grid.

If you click on any square, an X should show up in it.

View the current code.

The React Devtools extension for Chrome and Firefox lets you inspect a React component tree in your browser devtools.

[站外图片上传中...(image-d9c2c3-1515587905953)]

It lets you inspect the props and state of any of the components in your tree.

After installing it, you can right-click any element on the page, click “Inspect” to open the developer tools, and the React tab will appear as the last tab to the right.

However, note there are a few extra steps to get it working with CodePen:

We now have the basic building blocks for a tic-tac-toe game. But right now, the state is encapsulated in each Square component. To make a fully-working game, we now need to check if one player has won the game, and alternate placing X and O in the squares. To check if someone has won, we’ll need to have the value of all 9 squares in one place, rather than split up across the Square components.

You might think that Board should just inquire what the current state of each Square is. Although it is technically possible to do this in React, it is discouraged because it tends to make code difficult to understand, more brittle, and harder to refactor.

Instead, the best solution here is to store this state in the Board component instead of in each Square – and the Board component can tell each Square what to display, like how we made each square display its index earlier.

When you want to aggregate data from multiple children or to have two child components communicate with each other, move the state upwards so that it lives in the parent component. The parent can then pass the state back down to the children via props, so that the child components are always in sync with each other and with the parent.

Pulling state upwards like this is common when refactoring React components, so let’s take this opportunity to try it out. Add a constructor to the Board and set its initial state to contain an array with 9 nulls, corresponding to the 9 squares:

We’ll fill it in later so that a board looks something like

Board’s renderSquare method currently looks like this:

Modify it to pass a value prop to Square.

View the current code.

Now we need to change what happens when a square is clicked. The Board component now stores which squares are filled, which means we need some way for Square to update the state of Board. Since component state is considered private, we can’t update Board’s state directly from Square.

The usual pattern here is pass down a function from Board to Square that gets called when the square is clicked. Change renderSquare in Board again so that it reads:

We split the returned element into multiple lines for readability, and added parentheses around it so that JavaScript doesn’t insert a semicolon after return and break our code.

Now we’re passing down two props from Board to Square: value and onClick . The latter is a function that Square can call. Let’s make the following changes to Square:

After these changes, the whole Square component looks like this:

Now when the square is clicked, it calls the onClick function that was passed by Board. Let’s recap what happens here:

Note that DOM <button> element’s onClick attribute has a special meaning to React, but we could have named Square’s onClick prop or Board’s handleClick method differently. It is, however, conventional in React apps to use on* names for the attributes and handle* for the handler methods.

Try clicking a square – you should get an error because we haven’t defined handleClick yet. Add it to the Board class.

View the current code.

We call .slice() to copy the squares array instead of mutating the existing array. Jump ahead a section to learn why immutability is important.

Now you should be able to click in squares to fill them again, but the state is stored in the Board component instead of in each Square, which lets us continue building the game. Note how whenever Board’s state changes, the Square components rerender automatically.

Square no longer keeps its own state; it receives its value from its parent Board and informs its parent when it’s clicked. We call components like this controlled components .

In the previous code example, we suggest using the .slice() operator to copy the squares array prior to making changes and to prevent mutating the existing array. Let’s talk about what this means and why it is an important concept to learn.

There are generally two ways for changing data. The first method is to mutate the data by directly changing the values of a variable. The second method is to replace the data with a new copy of the object that also includes desired changes.

The end result is the same but by not mutating (or changing the underlying data) directly we now have an added benefit that can help us increase component and overall application performance.

Immutability also makes some complex features much easier to implement. For example, further in this tutorial we will implement time travel between different stages of the game. Avoiding data mutations lets us keep a reference to older versions of the data, and switch between them if we need to.

Determining if a mutated object has changed is complex because changes are made directly to the object. This then requires comparing the current object to a previous copy, traversing the entire object tree, and comparing each variable and value. This process can become increasingly complex.

Determining how an immutable object has changed is considerably easier. If the object being referenced is different from before, then the object has changed. That’s it.

The biggest benefit of immutability in React comes when you build simple pure components . Since immutable data can more easily determine if changes have been made, it also helps to determine when a component requires being re-rendered.

To learn more about shouldComponentUpdate() and how you can build pure components take a look at Optimizing Performance .

We’ve removed the constructor, and in fact, React supports a simpler syntax called functional components for component types like Square that only consist of a render method. Rather than define a class extending React.Component , simply write a function that takes props and returns what should be rendered.

Replace the whole Square class with this function:

You’ll need to change this.props to props both times it appears. Many components in your apps will be able to be written as functional components: these components tend to be easier to write and React will optimize them more in the future.

While we’re cleaning up the code, we also changed onClick=() => props.onClick() to just onClick=props.onClick , as passing the function down is enough for our example. Note that onClick=props.onClick() would not work because it would call props.onClick immediately instead of passing it down.

View the current code.

An obvious defect in our game is that only X can play. Let’s fix that.

Let’s default the first move to be by ‘X’. Modify our starting state in our Board constructor:

Each time we move we shall toggle xIsNext by flipping the boolean value and saving the state. Now update Board’s handleClick function to flip the value of xIsNext :

Now X and O take turns. Next, change the “status” text in Board’s render so that it also displays who is next:

After these changes you should have this Board component:

View the current code.

Let’s show when a game is won. Add this helper function to the end of the file:

You can call it in Board’s render function to check if anyone has won the game and make the status text show “Winner: [X/O]” when someone wins.

Replace the status declaration in Board’s render with this code:

You can now change handleClick in Board to return early and ignore the click if someone has already won the game or if a square is already filled:

Congratulations! You now have a working tic-tac-toe game. And now you know the basics of React. So you’re probably the real winner here.

View the current code.

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

react入门学习:井字棋游戏

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

文章目录

一.创建项目

  • 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
    

    此时应该能看到空白网页

遇到的问题

npx create-react-app my-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" />
        <!--
          manifest.json provides metadata used when your web app is installed on a
          user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
        -->
        <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
        <!--
          Notice the use of %PUBLIC_URL% in the tags above.
          It will be replaced with the URL of the `public` folder during the build.
          Only files inside the `public` folder can be referenced from the HTML.
    
          Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
          work correctly both with client-side routing and a non-root public URL.
          Learn how to configure a non-root public URL by running `npm run build`.
        -->
        <title>React App</title>
      </head>
      <body>
        <noscript>You need to enable JavaScript to run this app.</noscript>
        <div id="root"></div>
        <!--
          This HTML file is a template.
          If you open it directly in the browser, you will see an empty page.
    
          You can add webfonts, meta tags, or analytics to this file.
          The build step will place the bundled scripts into the <body> tag.
    
          To begin the development, run `npm start` or `yarn start`.
          To create a production bundle, use `npm run build` or `yarn build`.
        -->
      </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 (
            	<button className="square"> </button>
    		);
            //return后面的内容加括号,防止VScode等编辑器自动在return后面加分号
        
    
    
    class Board extends React.Component 
        
        renderSquare(i)
            return <Square />;
        
        
        render() 
            const status = 'Next player:X';
            
            return (
            	<div>
                	<div className = "status">
                    	status
                    </div>
                    
                    <div className = "board-new">
                    	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 
        render() 
            return (
            	<div className = "game">
                	<div className = "game-board">
                    	<Bpard />
                    </div>
                    
                    <div className = "game-info">
                    	<div> </div>
                        <ol> </ol>
                    </div>
                </div>
            )
        
    
    
    ReactDOM.render(
    	<Game />,
        document.getElementById('root')
    )
    
  • 初始代码中有三个React组件

    • Square
      • 渲染了单独的<button>
    • 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(
    			<button className="square">
    				this.props.value
    			</button>
    		);
    	
    
    
  • 修改后,在my-app路径下打开终端使用npm start启动项目

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

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

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

4.给组件添加交互

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

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

    增加onClick属性

    class Square extends React.Component 
        render() 
            return (
                <button className="square" onClick=() => alert('click')>this.props.value</button>
            );
           
    
    

    箭头函数也可以写为

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

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

  • 效果

给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 (
    			<button className="square" οnclick=()=?alert('click')>
    			this.props.value
    			</button>
    		);
    	
    
    

    在所有含有构造函数的的 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 (
          <button
            className="square"
            onClick=() => this.setState(value: 'X')
          >
            this.state.value
          </button>
        );
      
    
    

    这样子就能实现:在每次<button>被点击的时候通知React去重新渲染Square组件

    组件更新后,Square组件的this.state.value的值会变为'X'

    每次在组件中调用setState,React都会自动更新子组件

三.游戏完善

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

1.状态提升

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

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

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

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

        renderSquare(i) 
            return <Square value=this.state.squares[i] />;
        
    

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

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

     renderSquare(i) 
            return (<Square value=this.state.squares[i]
                onClick=() => this.handleClick(i)
            />);
        
      
    
  • 再其次,从Board组件向Square组件中传递valueonClick两个props参数

    需要修改Square的代码

    class Square extends React.Component 
    	render()
    		return (
    			<button 
    				className="square"
    				onClick=()=>this.props.onClick()
    			>
    			this.props.value
    			</button>
    		);
    	
    
    
  • 当每一个Square被点击时,Board提供的onClick函数就会触发,这是如何实现的呢?

  • 于是,添加handleClick方法

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

2.函数式组件

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

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

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

    可以注意到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,
            );
        
    
  • 效果

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

    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 (
                <div>
                    <div className="status">
                        status
                    </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>
            )
        
    

    效果:

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];
  • 接着,在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 (
                <div>
                    <div className="status">
                        status
                    </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>
            )
        
    
  • 效果

  • 此时还有个问题

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

    还有个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 (
    // 			<button 
    // 				className="square"
    // 				onClick=()=>this.props.onClick()
    // 			>
    // 			this.props.value
    // 			</button>
    // 		);
    // 	
    // 
    function Square(props) 
    
        return (
            <button className="square" onClick=props.onClick>
                props.value
            </button>
        );
    
    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 (<Square value=this.state.squares[i]
                onClick=() => 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 (
                <div>
                    <div className="status">
                        status
                    </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 
        render() 
            return (
                <div className="game">
                    <div className="game-board">
                        <Board />
                    </div>
                    <div className="game-info">
                        <div> </div>
                        <ol> </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]) 
                return squares[a];
            
    
        
        return null;
    
    
    

四.历史记录功能

1.提升状态

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

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

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

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

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

    • 修改后的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 (<Square value=this.props.squares[i]
                  onClick=() => 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 (
                  <div>
                      <div className="status">
                          status
                      </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>
              )
          
      
      
  • 接着,修改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 (
                <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> </ol>
                    </div>
                </div>
            )
        
    
    
    • 因为Game组件渲染了游戏状态,所以可以把Board组件的render方法中的对应代码移除,修改Board组件如下

      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>
          );
        
      
      
    • 还要把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,
                  ])react入门学习:井字棋游戏(官方文档教程)

      使用 Vue.js 改写 React 的官方教程井字棋

      React官网入门项目井字棋游戏

      p5结合做自动井字小游戏

      p5结合做自动井字小游戏

      python基础游戏之井字棋(讲解)