React——教程 && 零基础入门 && 从实践中学习(待续)
Tutorial: Intro to React
This tutorial doesn’t assume any existing React knowledge.
Tip
This tutorial is designed for people who prefer to learn by doing. If you prefer learning concepts from the ground up, check out our step-by-step guide. You might find this tutorial and the guide complementary to each other.
The tutorial is divided into several sections:
- Setup for the Tutorial will give you a starting point to follow the tutorial.
- Overview will teach you the fundamentals of React: components, props, and state.
- Completing the Game will teach you the most common techniques in React development.
- Adding Time Travel will give you a deeper insight into the unique strengths of React.
You don’t have to complete all of the sections at once to get the value out of this tutorial. Try to get as far as you can — even if it’s one or two sections.
What Are We Building?
In this tutorial, we’ll show how to build an interactive tic-tac-toe game with React.
You can see what we’ll be building here: Final Result. If the code doesn’t make sense to you, or if you are unfamiliar with the code’s syntax, don’t worry! The goal of this tutorial is to help you understand React and its syntax.
We recommend that you check out the tic-tac-toe game before continuing with the tutorial. One of the features that you’ll notice is that there is a numbered list to the right of the game’s board. This list gives you a history of all of the moves that have occurred in the game, and is updated as the game progresses.
You can close the tic-tac-toe game once you’re familiar with it. We’ll be starting from a simpler template in this tutorial. Our next step is to set you up so that you can start building the game.
Prerequisites
We’ll assume that you have some familiarity with HTML and JavaScript, but you should be able to follow along even if you’re coming from a different programming language. We’ll also assume that you’re familiar with programming concepts like functions, objects, arrays, and to a lesser extent, classes.
If you need to review 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.
Setup for the Tutorial
There are two ways to complete this tutorial: you can either write the code in your browser, or you can set up a local development environment on your computer.
Setup Option 1: Write Code in the Browser
This is the quickest way to get started!
First, open this Starter Code in a new tab. The new tab should display an empty tic-tac-toe game board and React code. We will be editing the React code in this tutorial.
You can now skip the second setup option, and go to the Overview section to get an overview of React.
Setup Option 2: Local Development Environment
This is completely optional and not required for this tutorial!
Optional: Instructions for following along locally using your preferred text editor
This setup requires more work but allows you to complete the tutorial using an editor of your choice. Here are the steps to follow:
- Make sure you have a recent version of Node.js installed.
- Follow the installation instructions for Create React App to make a new project.
npx create-react-app my-app
- Delete all files in the
src/
folder of the new project
Note:
Don’t delete the entire src
folder, just the original source files inside it. We’ll replace the default source files with examples for this project in the next step.
cd my-app
cd src
# If you're using a Mac or Linux:
rm -f *
# Or, if you're on Windows:
del *
# Then, switch back to the project folder
cd ..
-
Add a file named
index.css
in thesrc/
folder with this CSS code. -
Add a file named
index.js
in thesrc/
folder with this JS code. -
Add these three lines to the top of
index.js
in thesrc/
folder:
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.
Overview
Now that you’re set up, let’s get an overview of React!
What Is React?
React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called “components”.
React has a few different kinds of components, but we’ll start with React.Component
subclasses:
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 soon. We use components to tell React what we want to see on the screen. When our data changes, React will efficiently update and re-render our components.
Here, ShoppingList is a React component class, or React component type. A component takes in parameters, called props
(short for “properties”), and returns a hierarchy of views to display via the render
method.
The render
method returns a description of what you want to see on the screen. React takes the description and displays the result. 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 these structures easier to write. The <div />
syntax is transformed at build time to React.createElement('div')
. The example above is equivalent to:
return React.createElement('div', {className: 'shopping-list'},
React.createElement('h1', /* ... h1 children ... */),
React.createElement('ul', /* ... ul children ... */)
);
If you’re curious, createElement()
is described in more detail in the API reference, but we won’t be using it in this tutorial. Instead, we will keep using JSX.
JSX comes with the full power of JavaScript. You can put anyJavaScript expressions within braces inside JSX. Each React element is a JavaScript object that you can store in a variable or pass around in your program.
The ShoppingList
component above only renders built-in DOM components like <div />
and <li />
. But you can compose and render custom React components too. For example, we can now refer to the whole shopping list by writing <ShoppingList />
. Each React component is encapsulated and can operate independently; this allows you to build complex UIs from simple components.
Inspecting the Starter Code
If you’re going to work on the tutorial in your browser, open this code in a new tab: Starter Code. If you’re going to work on the tutorial locally, instead open src/index.js
in your project folder (you have already touched this file during the setup).
This Starter Code is the base of what we’re building. We’ve provided the CSS styling so that you only need to focus on learning React and programming the tic-tac-toe game.
By inspecting the code, you’ll notice that we have three React components:
- Square
- Board
- Game
The Square component renders a single <button>
and the Board renders 9 squares. The Game component renders a board with placeholder values which we’ll modify later. There are currently no interactive components.
Passing Data Through Props
To get our feet wet, let’s try passing some data from our Board component to our Square component.
We strongly recommend typing code by hand as you’re working through the tutorial and not using copy/paste. This will help you develop muscle memory and a stronger understanding.
In Board’s renderSquare
method, change the code to pass a prop called value
to the Square:
class Board extends React.Component {
renderSquare(i) {
return <Square value={i} />;
}
}
Change Square’s render
method to show that value by replacing {/* TODO */}
with {this.props.value}
:
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.value}
</button>
);
}
}
View the full code at this point
Congratulations! You’ve just “passed a prop” from a parent Board component to a child Square component. Passing props is how information flows in React apps, from parents to children.
Making an Interactive Component
Let’s fill the Square component with an “X” when we click it. First, change the button tag that is returned from the Square component’s render()
function to this:
class Square extends React.Component {
render() {
return (
<button className="square" onClick={function() { alert('click'); }}>
{this.props.value}
</button>
);
}
}
If you click on a Square now, you should see an alert in your browser.
Note
To save typing and avoid the confusing behavior of this
, we will use the arrow function syntax for event handlers here and further below:
class Square extends React.Component {
render() {
return (
<button className="square" onClick={() => alert('click')}>
{this.props.value}
</button>
);
}
}
Notice how with onClick={() => alert('click')}
, we’re passing a function as the onClick
prop. React will only call this function after a click. Forgetting () =>
and writing onClick={alert('click')}
is a common mistake, and would fire the alert every time the component re-renders.
As a next step, we want the Square component to “remember” that it got clicked, and fill it with an “X” mark. To “remember” things, components use state.
React components can have state by setting this.state
in their constructors. this.state
should be considered as private to a React component that it’s defined in. Let’s store the current value of the Square in this.state
, and change it when the Square is clicked.
First, we’ll add a constructor to the class to initialize the state:
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>
);
}
}
Note
In JavaScript classes, you need to always call
super
when defining the constructor of a subclass. All React component classes that have aconstructor
should start it with asuper(props)
call.
Now we’ll change the Square’s render
method to display the current state’s value when clicked:
- Replace
this.props.value
withthis.state.value
inside the<button>
tag. - Replace the
onClick={...}
event handler withonClick={() => this.setState({value: 'X'})}
. - Put the
className
andonClick
props on separate lines for better readability.
After these changes, the <button>
tag that is returned by the Square’s render
method looks like this:
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>
);
}
}
By calling this.setState
from an onClick
handler in the Square’s render
method, we tell React to re-render that Square whenever its <button>
is clicked. After the update, the Square’s this.state.value
will be 'X'
, so we’ll see the X
on the game board. If you click on any Square, an X
should show up.
When you call setState
in a component, React automatically updates the child components inside of it too.
Developer Tools
The React Devtools extension for Chrome and Firefox lets you inspect a React component tree with your browser’s developer tools.
The React DevTools let you check the props and the state of your React components.
After installing React DevTools, you can right-click on any element on the page, click “Inspect” to open the developer tools, and the React tabs (“⚛️ Components” and “⚛️ Profiler”) will appear as the last tabs to the right. Use “⚛️ Components” to inspect the component tree.
However, note there are a few extra steps to get it working with CodePen:
- Log in or register and confirm your email (required to prevent spam).
- Click the “Fork” button.
- Click “Change View” and then choose “Debug mode”.
- In the new tab that opens, the devtools should now have a React tab.
Completing the Game
We now have the basic building blocks for our tic-tac-toe game. To have a complete game, we now need to alternate placing “X”s and “O”s on the board, and we need a way to determine a winner.
Lifting State Up
Currently, each Square component maintains the game’s state. To check for a winner, we’ll maintain the value of each of the 9 squares in one location.
We may think that Board should just ask each Square for the Square’s state. Although this approach is possible in React, we discourage it because the code becomes difficult to understand, susceptible to bugs, and hard to refactor. Instead, the best approach is to store the game’s state in the parent Board component instead of in each Square. The Board component can tell each Square what to display by passing a prop, just like we did when we passed a number to each Square.
To collect data from multiple children, or to have two child components communicate with each other, you need to declare the shared state in their parent component instead. The parent component can pass the state back down to the children by using props; this keeps the child components in sync with each other and with the parent component.
Lifting state into a parent component is common when React components are refactored — let’s take this opportunity to try it out.
Add a constructor to the Board and set the Board’s initial state to contain an array of 9 nulls corresponding to the 9 squares:
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
renderSquare(i) {
return <Square value={i} />;
}
When we fill the board in later, the this.state.squares
array will look something like this:
[
'O', null, 'X',
'X', 'X', 'O',
'O', null, null,
]
The Board’s renderSquare
method currently looks like this:
renderSquare(i) {
return <Square value={i} />;
}
In the beginning, we passed the value
prop down from the Board to show numbers from 0 to 8 in every Square. In a different previous step, we replaced the numbers with an “X” mark determined by Square’s own state. This is why Square currently ignores the value
prop passed to it by the Board.
We will now use the prop passing mechanism again. We will modify the Board to instruct each individual Square about its current value ('X'
, 'O'
, or null
). We have already defined the squares
array in the Board’s constructor, and we will modify the Board’s renderSquare
method to read from it:
renderSquare(i) {
return <Square value={this.state.squares[i]} />;
}
View the full code at this point
Each Square will now receive a value
prop that will either be 'X'
, 'O'
, or null
for empty squares.
Next, we need to change what happens when a Square is clicked. The Board component now maintains which squares are filled. We need to create a way for the Square to update the Board’s state. Since state is considered to be private to a component that defines it, we cannot update the Board’s state directly from Square.
Instead, we’ll pass down a function from the Board to the Square, and we’ll have Square call that function when a square is clicked. We’ll change the renderSquare
method in Board to:
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
Note
We split the returned element into multiple lines for readability, and added parentheses 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 onClick
prop is a function that Square can call when clicked. We’ll make the following changes to Square:
- Replace
this.state.value
withthis.props.value
in Square’srender
method - Replace
this.setState()
withthis.props.onClick()
in Square’srender
method - Delete the
constructor
from Square because Square no longer keeps track of the game’s state
After these changes, the Square component looks like this:
class Square extends React.Component {
render() {
return (
<button
className="square"
onClick={() => this.props.onClick()}
>
{this.props.value}
</button>
);
}
}
When a Square is clicked, the onClick
function provided by the Board is called. Here’s a review of how this is achieved:
- The
onClick
prop on the built-in DOM<button>
component tells React to set up a click event listener. - When the button is clicked, React will call the
onClick
event handler that is defined in Square’srender()
method. - This event handler calls
this.props.onClick()
. The Square’sonClick
prop was specified by the Board. - Since the Board passed
onClick={() => this.handleClick(i)}
to Square, the Square callsthis.handleClick(i)
when clicked. - We have not defined the
handleClick()
method yet, so our code crashes. If you click a square now, you should see a red error screen saying something like “this.handleClick is not a function”.
Note
The DOM <button>
element’s onClick
attribute has a special meaning to React because it is a built-in component. For custom components like Square, the naming is up to you. We could give any name to the Square’s onClick
prop or Board’s handleClick
method, and the code would work the same. In React, it’s conventional to use on[Event]
names for props which represent events and handle[Event]
for the methods which handle the events.
When we try to click a Square, we should get an error because we haven’t defined handleClick
yet. We’ll now add handleClick
to the Board class:
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