This tutorial doesn’t assume any existing React knowledge.
Đang xem: đồ án reactjs
We will build a small game during this tutorial. You might be tempted to skip it because you’re not building games — but give it a chance. The techniques you’ll learn in the tutorial are fundamental to building any React app, and mastering it will give you a deep understanding of React.
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.
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 it 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.
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.
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.
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.
Delete all files in the src/ folder of the new project
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 the src/ folder with this CSS code. Add a file named index.js in the src/ folder with this JS code. Add these three lines to the top of index.js in the src/ folder:
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 help quickly. If you don’t receive an answer, or if you remain stuck, please file an issue, and we’ll help you out.
Now that you’re set up, let’s get an overview of React!
React has a few different kinds of components, but we’ll start with React.Component subclasses:
class ShoppingList extends React.Component </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”, , 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 in this tutorial. Instead, we will keep using JSX.
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.
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.
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:
After: You should see a number in each square in the rendered output.
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.
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:
If you click on a Square now, you should see an alert in your browser.
To save typing and avoid the confusing behavior of this, we will use the arrow function syntax for event handlers here and further below:
Notice how with onClick=, we’re passing a function as the onClick prop. React will only call this function after a click. Forgetting () => and writing onClick= 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:
Now we’ll change the Square’s render method to display the current state’s value when clicked:
Replace this.props.value with this.state.value inside the <button> tag. Replace the onClick= event handler with onClick=)}. Put the className and onClick 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 ; } render() )} > </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.
View the full code at this point
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.
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.
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:
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:
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:
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 with this.props.value in Square’s render method Replace this.setState() with this.props.onClick() in Square’s render 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:
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’s render() method. This event handler calls this.props.onClick(). The Square’s onClick prop was specified by the Board. Since the Board passed onClick= to Square, the Square calls this.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”.
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
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 ; } handleClick(i) ); } renderSquare(i) onClick= /> ); } render() </div> <div className=“board-row“> </div> <div className=“board-row“> </div> <div className=“board-row“> </div> </div> ); } }
View the full code at this point
After these changes, we’re again able to click on the Squares to fill them, the same as we had before. However, now the state is stored in the Board component instead of the individual Square components. When the Board’s state changes, the Square components re-render automatically. Keeping the state of all squares in the Board component will allow it to determine the winner in the future.
Since the Square components no longer maintain state, the Square components receive values from the Board component and inform the Board component when they’re clicked. In React terms, the Square components are now controlled components. The Board has full control over them.
Note how in handleClick, we call .slice() to create a copy of the squares array to modify instead of modifying the existing array. We will explain why we create a copy of the squares array in the next section.
In the previous code example, we suggested that you use the .slice() method to create a copy of the squares array to copy instead of modifying the existing array. We’ll now discuss immutability and why immutability is important to learn.
There are generally two approaches to changing data. The first approach is to mutate the data by directly changing the data’s values. The second approach is to replace the data with a new copy which has the desired changes.
Data Change with Mutation
var player = ; var newPlayer = Object.assign(, player, ); // Now player is unchanged, but newPlayer is // Or if you are using object spread syntax proposal, you can write: // var newPlayer = ;
The end result is the same but by not mutating (or changing the underlying data) directly, we gain several benefits described below.
Complex Features Become Simple
Immutability makes complex features much easier to implement. Later in this tutorial, we will implement a “time travel” feature that allows us to review the tic-tac-toe game’s history and “jump back” to previous moves. This functionality isn’t specific to games — an ability to undo and redo certain actions is a common requirement in applications. Avoiding direct data mutation lets us keep previous versions of the game’s history intact, and reuse them later.
Detecting changes in mutable objects is difficult because they are modified directly. This detection requires the mutable object to be compared to previous copies of itself and the entire object tree to be traversed.
Detecting changes in immutable objects is considerably easier. If the immutable object that is being referenced is different than the previous one, then the object has changed.
Determining When to Re-Render in React
The main benefit of immutability is that it helps you build pure components in React. Immutable data can easily determine if changes have been made, which helps to determine when a component requires re-rendering.
You can learn more about shouldComponentUpdate() and how you can build pure components by reading Optimizing Performance.
We’ll now change the Square to be a function component.
In React, function components are a simpler way to write components that only contain a render method and don’t have their own state. Instead of defining a class which extends React.Component, we can write a function that takes props as input and returns what should be rendered. Function components are less tedious to write than classes, and many components can be expressed this way.
Replace the Square class with this function:
We have changed this.props to props both times it appears.
View the full code at this point
When we modified the Square to be a function component, we also changed onClick= to a shorter onClick= (note the lack of parentheses on both sides).
We now need to fix an obvious defect in our tic-tac-toe game: the “O”s cannot be marked on the board.
We’ll set the first move to be “X” by default. We can set this default by modifying the initial state in our Board constructor:
Each time a player moves, xIsNext (a boolean) will be flipped to determine which player goes next and the game’s state will be saved. We’ll update the Board’s handleClick function to flip the value of xIsNext:
With this change, “X”s and “O”s can take turns. Try it!
Let’s also change the “status” text in Board’s render so that it displays which player has the next turn:
render() ; } handleClick(i) ); } renderSquare(i) onClick= /> ); } render() </div> <div className=“board-row“> </div> <div className=“board-row“> </div> <div className=“board-row“> </div> </div> ); } }
View the full code at this point
Now that we show which player’s turn is next, we should also show when the game is won and there are no more turns to make. Copy this helper function and paste it at the end of the file:
Given an array of 9 squares, this function will check for a winner and return 'X', 'O', or null as appropriate.
We will call calculateWinner(squares) in the Board’s render function to check if a player has won. If a player has won, we can display text such as “Winner: X” or “Winner: O”. We’ll replace the status declaration in Board’s render function with this code:
We can now change the Board’s handleClick function to return early by ignoring a click if someone has won the game or if a Square is already filled:
View the full code at this point
Congratulations! You now have a working tic-tac-toe game. And you’ve just learned the basics of React too. So you’re probably the real winner here.
As a final exercise, let’s make it possible to “go back in time” to the previous moves in the game.
If we mutated the squares array, implementing time travel would be very difficult.
However, we used slice() to create a new copy of the squares array after every move, and treated it as immutable. This will allow us to store every past version of the squares array, and navigate between the turns that have already happened.
We’ll store the past squares arrays in another array called history. The history array represents all board states, from the first to the last move, and has a shape like this:
history = < // Before first move , // After first move , // After second move , // … >
Now we need to decide which component should own the history state.
We’ll want the top-level Game component to display a list of past moves. It will need access to the history to do that, so we will place the history state in the top-level Game component.
Placing the history state into the Game component lets us remove the squares state from its child Board component. Just like we “lifted state up” from the Square component into the Board component, we are now lifting it up from the Board into the top-level Game component. This gives the Game component full control over the Board’s data, and lets it instruct the Board to render previous turns from the history.
First, we’ll set up the initial state for the Game component within its constructor:
class Game extends React.Component >, xIsNext: true, }; } render() </div> <ol></ol> </div> </div> ); } }
Next, we’ll have the Board component receive squares and onClick props from the Game component. Since we now have a single click handler in Board for many Squares, we’ll need to pass the location of each Square into the onClick handler to indicate which Square was clicked. Here are the required steps to transform the Board component:
Delete the constructor in Board. Replace this.state.squares with this.props.squares in Board’s renderSquare. Replace this.handleClick(i) with this.props.onClick(i) in Board’s renderSquare.
The Board component now looks like this:
class Board extends React.Component squares<i> = this.state.xIsNext ? “X” : “O”; this.setState(); } renderSquare(i) onClick= /> ); } render() else return ( <div> <div className=“status“></div> <div className=“board-row“> </div> <div className=“board-row“> </div> <div className=“board-row“> </div> </div> ); } }
We’ll update the Game component’s render function to use the most recent history entry to determine and display the game’s status:
render() else return ( <div className=“game“> <div className=“game-board“> <Board squares= onClick= /> </div> <div className=“game-info“> <div></div> <ol></ol> </div> </div> ); }
Since the Game component is now rendering the game’s status, we can remove the corresponding code from the Board’s render method. After refactoring, the Board’s render function looks like this:
render() </div> <div className=“board-row“> </div> <div className=“board-row“> </div> </div> ); }
Finally, we need to move the handleClick method from the Board component to the Game component. We also need to modify handleClick because the Game component’s state is structured differently. Within the Game’s handleClick method, we concatenate new history entries onto history.
handleClick(i) squares<i> = this.state.xIsNext ? “X” : “O”; this.setState(>), xIsNext: !this.state.xIsNext, }); }
Unlike the array push() method you might be more familiar with, the concat() method doesn’t mutate the original array, so we prefer it.
At this point, the Board component only needs the renderSquare and render methods. The game’s state and the handleClick method should be in the Game component.
View the full code at this point
Since we are recording the tic-tac-toe game’s history, we can now display it to the player as a list of past moves.
Using the map method, we can map our history of moves to React elements representing buttons on the screen, and display a list of buttons to “jump” to past moves.
Let’s map over the history in the Game’s render method:
render() ></button> </li> ); }); let status; if (winner) else return ( <div className=“game“> <div className=“game-board“> <Board squares= onClick= /> </div> <div className=“game-info“>Xem thêm bài viết thuộc chuyên mục: Đồ án