VDone Demo VDone Demo
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)

Nikolay Tuzov

Backend Developer
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)
  • 核心概念

  • 高级指引

  • Hook

  • 案例演示

    • Tutorial Demo
      • Demo Code
      • Why Immutability Is Important in React
        • Direct Data Mutation
        • Replacing Data with a New Copy
        • Simplifying Complex Features
        • Detecting Changes
        • Determining When to Re-Render in React
  • 《React》笔记
  • 案例演示
xugaoyi
2021-03-27
Contents

Tutorial Demo

# Tutorial Demo

This section is based on the official Tutorial (opens new window).

Online demo (opens new window)

# Demo Code

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'

// Square (can use a function component when there's no state)
function Square (props) {
  // return returns a React element
  return (
    // <button> is a built-in component, onClick is its built-in click handler prop
    <button
      className="square"
      onClick={props.onClick}
    >
      {props.value}
    </button>
  )
}

// Board component (collection of squares)
class Board extends React.Component {
  renderSquare (i) {
    // Parentheses prevent automatic semicolon insertion from breaking the structure
    return (
      // Pass two props to the Square component: value and onClick
      // Note: onClick here passes a prop, it doesn't bind a click event directly
      // Event naming convention: on[Event]={ this.handle[Event] }
      <Square
        value={this.props.squares[i]}
        onClick={() => this.props.onClick(i)}
      />
    )
  }

  // The render method describes what you want to see on screen
  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>
    )
  }
}

// Game component (outermost layer)
class Game extends React.Component {
  constructor(props) {
    // super is required in JavaScript class inheritance constructors
    super(props)
    // state is private to the current component
    this.state = {
      history: [
        {
          squares: Array(9).fill(null)
        }
      ],
      stepNumber: 0, // The history step currently being viewed
      xIsNext: true,
    }
  }

  // Handle square click (triggered by child component)
  handleClick (i) {
    // The slice here discards "future" history when going "back in time", and setState will erase the original "future" records.
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1]
    const squares = current.squares.slice() // slice() creates an array copy

    if (calculateWinner(squares) || squares[i]) { // If there's a winner or the square is already taken
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O'
    this.setState({
      // concat doesn't mutate the original array, while push does
      history: history.concat([{
        squares
      }]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext
    })
  }

  // Jump to a history step
  jumpTo (step) {
    // history is not modified here
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0
    })
  }

  render () {
    const history = this.state.history
    const current = history[this.state.stepNumber]
    const winner = calculateWinner(current.squares)

    // History steps (map transforms the original array into another array)
    const moves = history.map((step, move) => {
      const desc = move ?
        'Go to move #' + move :
        'Go to game start';
      return (
        <li key={move}>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
      )
    })

    let status
    if (winner) {
      status = 'Winner: ' + winner;
    } else {
      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
    }

    // Note: onClick here passes a prop, it doesn't bind a click event directly
    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>{moves}</ol>
        </div>
      </div>
    )
  }
}

ReactDOM.render(
  <Game />,
  document.getElementById('root')
)

// Calculate winner
function calculateWinner (squares) {
  // Winning combinations
  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],
  ];

  // Check if values in any winning combination are the same, and return the winner
  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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175

# Why Immutability Is Important in React

In the demo, when modifying state, we don't directly modify object properties. Instead, we first create a copy of the array using .slice(), then replace the entire array via setState().

Generally, there are two approaches to changing data. The first is to directly mutate the data by changing its values. The second is to replace the data with a new copy.

# Direct Data Mutation

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

# Replacing Data with a New Copy

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

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

// Using object spread syntax:
// var newPlayer = {...player, score: 2};
1
2
3
4
5
6
7

Not mutating (or changing the underlying data) achieves the same result, with several benefits:

# Simplifying Complex Features

Immutability makes complex features much easier to implement. Later in the 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 unique 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

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 (opens new window).

#

Edit (opens new window)
#React
Last Updated: 2026/03/21, 12:14:36
Custom Hooks

← Custom Hooks

Recent Updates
01
How I Discovered Disposable Email — A True Story
06-12
02
Animations in Grid Layout
09-15
03
Renaming a Git Branch
08-11
More Articles >
Theme by VDone | Copyright © 2026-2026 Nikolay Tuzov | MIT License | Telegram
  • Auto
  • Light Mode
  • Dark Mode
  • Reading Mode