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

Tutorial: Intro To React

Before We Start

What We’re Building

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.

教程:React介绍

开始之前

如何创建

今天,我们要做一个互动的井字游戏。 如果你喜欢,你可以在这里查看最终结果:最终结果。如果代码对您没有意义,或者使用不熟悉的语法,请不要担心。在本教程中,我们将学习如何一步步构建这个游戏。 尝试玩这个游戏。你也可以点击移动列表中的一个按钮来“回到过去”,看看在移动完成后的面板看起来是什么样子。 一旦您熟悉了这个游戏,就可以关闭该选项卡,因为我们将从下一节的更简单的模板开始。

Prerequisites

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.

前提条件

我们将假定您熟悉HTML和JavaScript,但是您应该能够了解,即使您以前没有使用过它们。 

如果您需要复习关于JavaScript的知识点,我们建议您阅读本指南。注意,我们还使用了ES6的一些特性,这是JavaScript的最新版本。在本教程中,我们将使用箭头函数、类、let和const语句。您可以使用Babel REPL检查ES6代码编译的内容。

How to Follow Along

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.

如何完成

有两种方法可以完成本教程:您可以在浏览器中编写代码,也可以在您的机器上设置一个本地开发环境。你可以根据自己的感觉选择任何一种选择。

If You Prefer to Write Code in the Browser

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.

如果您喜欢在浏览器中编写代码。 

这是最快的开始! 首先,在新选项卡中打开此启动程序代码。它应该显示一个空的井字游戏。我们将在本教程中编辑该代码。 
现在您可以跳过下一节,介绍如何设置本地开发环境并直接进入概述。

If You Prefer to Write Code in Your Editor

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:

  1. Make sure you have a recent version of Node.js installed.
  2. Follow the installation instructions to create a new project.
如果你想这样做,下面是一些步骤: 

1、确保您安装了最近版本的Node.js。 

2、按照安装说明创建一个新项目。 
npm install -g create-react-app
create-react-app my-app
  1. Delete all files in the src/ folder of the new project (don’t delete the folder, just its contents).
1、删除新项目的src/文件夹中的所有文件(不要删除文件夹,只删除其内容)。
cd my-app
rm -f src/*
  1. Add a file named index.css in the src/ folder with this CSS code.
  2. Add a file named index.js in the src/ folder with this JS code.
  3. Add these three lines to the top of index.js in the src/ folder:
1、添加一个名为index.css的文件,在src/文件夹下,并用输入这个css代码。 
2、添加一个名为index.js的文件,在src/文件夹下,并输入这个js代码。 
3、将这三行代码添加到index.js的顶部,在src/文件夹: 
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

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.

现在,如果您在项目文件夹中运行npm start,并在浏览器中打开http://localhost:3000,那么您应该会看到一个空的tic-tac-toe页面。 

我们建议按照这些说明为编辑器配置语法高亮显示。

Help, I’m Stuck!

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!

帮助,我遇到困难了!

如果遇到困难,请查看社区支持资源。特别是,Reactiflux chat是获得快速帮助的好方法。如果你在任何地方都找不到好的答案,请提出一个问题,我们会帮你解决。 

有了这些,让我们开始吧!

Overview

What is React?

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.Componentsubclasses:

概述 

React是什么? 

React是一个声明性的、高效的、灵活的JavaScript库,用于构建用户界面。 

React有几种不同的组件,但我们将从React.Component开始:
class ShoppingList extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
          <li>Oculus</li>
        </ul>
      </div>
    );
  }
}

// Example usage: <ShoppingList name="Mark" />

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 rendermethod.

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:

我们马上就会讲到有趣的类似xml的标签。您的组件告诉React您想要呈现的内容——然后当您的数据发生变化时,响应将实时地更新和呈现正确的组件。 

在这里,ShoppingList是一个React组件类,或React组件类型。组件接受参数,称为props,并返回通过render方法显示的视图层次结构。 

render方法返回您想要呈现的内容的描述,然后对该描述进行响应,并将其呈现。特别是,render返回一个React元素,它是对要呈现的内容的轻量级描述。大多数情况下,开发人员使用一种称为JSX的特殊语法,这使得编写这些结构更加容易。在构建时,<div />语法被转换为React.createElement('div')。上面的例子等价于:
return React.createElement('div', {className: 'shopping-list'},
  React.createElement('h1', /* ... h1 children ... */),
  React.createElement('ul', /* ... ul children ... */)
);

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.

如果您好奇的话,createElement()在API引用中会有更详细的描述,但是我们不会在本教程中直接使用它。相反,我们将继续使用JSX。 

您可以在JSX内的括号内放置任何JavaScript表达式。每个React元素都是一个真正的JavaScript对象,可以存储在一个变量中,也可以在程序中传递。 

ShoppingList组件只呈现内置的DOM组件,但是您可以通过编写<ShoppingList />,很轻松的组合成自定义React组件。每个组件都被封装起来,这样它就可以独立运行,这样就可以通过简单的组件构建复杂的ui。

Getting Started

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:

  • Square
  • Board
  • Game

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.

开始

从这个示例开始:启动代码。 

它包含了我们今天所创建项目的框架。我们已经提供了样式,所以您只需要担心JavaScript。 

具体来说,我们有三个组成部分: 

+ Square
+ Board
+ Game

Square组件呈现一个单独的<button>,Board呈现9个Square,Game组件呈现一个带有一些占位符的Board,稍后我们将填充它。在这一点上,没有一个组件是交互式的。

Passing Data Through Props

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:

使用Props传递数据

设定一个简单目标来开始你的学习或许会更好,让我们试着把一些数据从Board组件传递到Square组件。在Board的renderSquare方法中,更改代码将一个值传递给Square:
class Board extends React.Component {
  renderSquare(i) {
    return <Square value={i} />;
  }

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

然后,更改Square的render方法,以显示该值,用{this.props.value}替换{/* TODO */}。
class Square extends React.Component {
  render() {
    return (
      <button className="square">
        {this.props.value}
      </button>
    );
  }
}

Before:

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

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

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

View the current code.

An Interactive Component

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:

一个交互式组件 

当你点击它的时候,我们让正方形的部分填充一个“X”。试着改变这个方形的渲染render()函数中返回的按钮标签:
class Square extends React.Component {
  render() {
    return (
      <button className="square" onClick={() => alert('click')}>
        {this.props.value}
      </button>
    );
  }
}

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:

如果你现在点击一个正方形,会在浏览器中得到一个警告。 

这是因为使用了新的JavaScript箭头函数语法。注意,我们正在传递一个函数作为onClick的prop。onClick={alert('click')}会立即发出警报,而不是单击按钮。 

React组件可以通过在构造函数中使用this.state来设置状态,它应该被视为组件的私有属性。让我们将这个正方形的当前值存储在状态中,并在单击square时更改它。 

首先,在类中添加一个构造函数来初始化状态:
class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null,
    };
  }

  render() {
    return (
      <button className="square" onClick={() => alert('click')}>
        {this.props.value}
      </button>
    );
  }
}

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:

  • Replace this.props.value with this.state.value inside the <button> tag.
  • Replace the () => alert() event handler with () => this.setState({value: 'X'}).

Now the <button> tag looks like this:

在JavaScript类中,当定义子类的构造函数时,需要显式地调用super();。 

现在更改Square render方法,以显示当前状态的值,并单击: 

+ 用this.state.value替换this.props.value,放在<button>内。
+ 将()=> alert()事件处理程序替换为() => this.setState({value: 'X'})。 

现在,标签看起来是这样的:
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>
    );
  }
}

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.

无论何时,调用this.setState,对组件的进行更新,在通过的状态更新中导致响应合并,并将组件与它的后代一起重新运行。当组件重新运行时,this.state.value是X,你会看到网格中的X。 

如果你点击任何一个正方形,就会出现一个X。 

查看当前的代码。

Developer Tools

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.

开发人员工具 

Chrome和Firefox的响应Devtools扩展允许您在浏览器Devtools中检查一个React组件树。 

它允许您检查树中的任何组件的props和state。

在安装之后,您可以右键单击页面上的任何元素,单击“检查”打开开发工具,而React选项卡将显示为右边的最后一个选项卡。

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

  1. Log in or register and confirm your email (required to prevent spam).
  2. Click the “Fork” button.
  3. Click “Change View” and then choose “Debug mode”.
  4. In the new tab that opens, the devtools should now have a React tab.
但是,请注意,有一些额外的步骤可以让它与CodePen一起工作: 

1、登录或注册并确认您的电子邮件(需要防止垃圾邮件)。 
2、单击“叉”按钮。 
3、点击“更改视图”,然后选择“调试模式”。 
4、在打开的新选项卡中,devtools现在应该有一个React选项卡。

Lifting State Up

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.

解除状态

我们现在有了一个井字游戏的基本框架。但是现在,状态被封装在每个正方形组件中。要做一个完整的游戏,我们现在需要检查一个玩家是否赢得了游戏,并且轮流在方块中放置X和O。为了检查是否有人赢了,我们需要在一个地方拥有所有9个方块的值,而不是在正方形的组件上分割。 

您可能会认为,Board应该询问每个正方形的当前状态是什么。虽然在技术上有可能这样做,但并不鼓励这样做,因为它往往使代码难于理解,更脆弱,而且更难重构。 

相反,这里最好的解决方案是将这个状态存储在Board组件中,而不是在每个方块中,而Board组件可以告诉每个方块显示什么,比如我们如何让每个方块更早地显示它的索引。

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.

当您想要从多个子节点聚合数据或让两个子组件相互通信时,请将状态向上传递,以使其位于父组件中。然后,父类可以通过props将状态传递给子节点,这样子组件总是与父组件和父组件保持同步。

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:

在重构组件时,像这样向上传递状态是很常见的,所以让我们利用这个机会尝试一下。向Board中添加构造函数,并将其初始状态设置为包含9个null的数组,对应于9个方块:
class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
    };
  }

  renderSquare(i) {
    return <Square value={i} />;
  }

  render() {
    const status = 'Next player: X';

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

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

我们将会把它加进去,这样Board看起来就像这样。
[
  'O', null, 'X',
  'X', 'X', 'O',
  'O', null, null,
]

Board’s renderSquare method currently looks like this:

Board的renderSquare方法现在看起来是这样的:
renderSquare(i) {
  return <Square value={i} />;
}

Modify it to pass a value prop to Square.

修改它以传递一个value prop到Square。
renderSquare(i) {
    return <Square value={this.state.squares[i]} />;
}

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:

查看当前的代码。

现在我们需要当一个正方形被点击时发生一些改变。现在,Board组件存储了哪些方块是填充的,这意味着我们需要一些方法来更新棋盘的状态。由于组件状态被认为是私有的,所以我们不能直接从Square更新Board的状态。

这里通常的模式是将一个函数从棋盘向下传递到Square被单击时调用的方块。再次更改renderSquare,以便它读取:
renderSquare(i) {
    return (
        <Square value={this.state.squares[i]} onClick={() => this.handleClick(i)}/>
    );
}

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:

  • Replace this.state.value with this.props.value in Square’s render.
  • Replace this.setState() with this.props.onClick() in Square’s render.
  • Delete constructor definition from Square because it doesn’t have state anymore.

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

我们将返回的元素拆分为多个行,以获得可读性,并在其周围加上括号,这样JavaScript就不会在返回后插入分号,并破坏我们的代码。

现在,我们从Board到Square有两个props值:value和onClick。后者是Square可以调用的函数。让我们对Square进行以下更改:

+ 在Square的render中,将this.state.value替换为this.props.value。
+ 在Square的render中,将this.setState()替换为this.props.onClick()。
+ 从Square中删除constructor函数定义,因为它不再具有状态。

在这些变化之后,整个方形组件看起来是这样的:
class Square extends React.Component {
  render() {
    return (
      <button className="square" onClick={() => this.props.onClick()}>
        {this.props.value}
      </button>
    );
  }
}

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

  1. The onClick prop on the built-in DOM <button> component tells React to set up a click event listener.
  2. When the button is clicked, React will call the onClick event handler defined in Square’s render() method.
  3. This event handler calls this.props.onClick(). Square’s props were specified by the Board.
  4. Board passed onClick={() => this.handleClick(i)} to Square, so, when called, it runs this.handleClick(i) on the Board.
  5. We have not defined the handleClick() method on the Board yet, so the code crashes.

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.

现在当单击square时,它调用由Board传递的onClick函数。让我们回顾一下这里发生了什么:

1、在内置的DOM <button>组件上的onClick prop告诉React设置一个单击事件监听器。
2、当单击按钮时,React将调用在Square组件中render()方法定义的onClick事件。
3、这个事件调用this.props.onclick()。Square的props是由Board指定的。
4、Board通过onClick={() => this.handleClick(i)将值给Square,所以,当被调用时,在Board中它运行this.handleClick(i)。
5、我们还没有在Board上定义handleClick()方法,因此代码崩溃。

注意,DOM 
<button></button>元素的onClick属性在React中有特殊意义,但我们可以用不同的方式命名Square的onClick prop或Board的handleClick方法。然而,React应用中传统的做法是用on*为是事件名,handle*为事件方法名。

试着点击一个正方形-你应该得到一个错误,因为我们还没有定义handleClick。把它加到Board上。
class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
    };
  }

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

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

  render() {
    const status = 'Next player: X';

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

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.

查看当前的代码。

我们调用.slice()来复制平方数组,而不是对现有的数组进行突变。跳过一节,学习为什么不变性很重要。

现在你应该可以点击方块来填充它们,但是状态存储在Board组件中而不是每个方块中,让我们继续构建游戏。请注意,每当Board状态发生变化时,Square组件会自动重新运行。

Square不再保持它自己的状态;它从它的父Board接收它的值,并在它被单击时通知它的父进程。我们称这些组件为受控组件。

Why Immutability Is Important

In the previous code example, we suggest using the .slice() operator to copy the squaresarray 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.

为什么不变性是很重要的

在前面的代码示例中,我们建议使用.slice()操作符在进行更改之前复制squares数组,并防止对现有数组进行更改。让我们来讨论一下这意味着什么,以及为什么学习是一个重要的概念。

通常有两种方式来改变数据。第一种方法是通过直接改变变量的值来改变数据。第二种方法是用对象的新副本替换数据,该对象还包括所需的更改。

Data change with mutation

var player = {score: 1, name: 'Jeff'};
player.score = 2;
// Now player is {score: 2, name: 'Jeff'}

Data change without mutation

var player = {score: 1, name: 'Jeff'};

var newPlayer = Object.assign({}, player, {score: 2});
// Now player is unchanged, but newPlayer is {score: 2, name: 'Jeff'}

// Or if you are using object spread syntax proposal, you can write:
// var newPlayer = {...player, score: 2};

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.

最终的结果是相同的,但是通过不直接改变(或更改底层数据),我们现在有了一个额外的好处,可以帮助我们增加组件和整体的应用程序性能。

Easier Undo/Redo and Time Travel

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.

轻松撤销/重做和时间旅行。

不变性也使得一些复杂的特性更容易实现。例如,在本教程中,我们将实现在游戏不同阶段之间的时间旅行。避免数据突变可以让我们保留对数据的旧版本的引用,并在需要时切换它们。

Tracking Changes

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.

跟踪变化

确定一个突变的对象是否发生了变化是复杂的,因为更改是直接对对象进行的。然后,需要将当前对象与之前的副本进行比较,遍历整个对象树,并比较每个变量和值。这个过程可能变得越来越复杂。

确定一个不可变对象的变化是相当容易的。如果被引用的对象与以前不同,那么对象就会改变。就是这样。

Determining When to Re-render in React

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.

要了解更多关于shouldComponentUpdate()的知识,以及如何构建纯组件,请查看优化性能。

Functional Components

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:

功能组件

我们已经删除了构造函数,实际上,它支持一种更简单的语法,称为组件类型的功能组件,例如只包含呈现方法的Square。而不是定义一个类扩展React.Component组件,简单地编写一个函数,该函数使用props并返回应该呈现的内容。

用这个函数替换整个Square类:
function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

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.onClickimmediately instead of passing it down.

View the current code.

你需要修改this.props为props。您的应用程序中的许多组件将能够被编写为功能组件:这些组件往往更容易编写和响应,从而在将来对它们进行更多的优化。

当我们在清理代码时,我们也修改了onClick={() => props.onClick()}来仅仅是修改为onClick={props.onClick},将函数向下传递就足够了。注意onClick={props.onClick()}不会起作用,因为它会调用props.onClick而不是将其传递下去。

查看当前的代码。

Taking Turns

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:

轮流变换

我们的游戏有一个明显的缺陷,那就是只有X可以玩。让我们解决这个问题。

让我们默认第一个动作是X。在我们的Board构造函数中修改我们的起始状态:
class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),
      xIsNext: true,
    };
  }

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:

每次移动时,我们都要通过变换布尔值和保存状态来切换xIsNext。现在更新Board的handleClick函数来变换xIsNext的值:
  handleClick(i) {
    const squares = this.state.squares.slice();
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      squares: squares,
      xIsNext: !this.state.xIsNext,
    });
  }

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

现在X和O轮流变换。接下来,改变“状态”文本,使它也显示谁是下一个:
render() {
    const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');

    return (
      // the rest has not changed

After these changes you should have this Board component:

在这些改变之后,你应该有这样的Board组件:
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();
    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 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>
    );
  }
}

View the current code.

Declaring a Winner

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

产生一个赢家

让我们看一场比赛获胜。将此helper函数添加到文件的末尾:
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;
}

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:

你可以在棋盘的渲染功能中调用它来检查是否有人赢了游戏,并且当有人获胜时,让状态文本显示“赢家:[X/O]”。

用这段代码替换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 (
      // the rest has not changed

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:

现在你可以改变handleClick来提早返回,如果有人已经赢了游戏或者已经填满了正方形,可以忽略点击。
  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,
    });
  }

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的基本原理了。所以你可能是真正的赢家。

查看当前代码
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容