使用 React 16.12 實作「井字遊戲」應用程式

前言

本文為 React 官方文件「學習指南:React 介紹」的學習筆記,實作與原文有些許差異。

建立專案

建立專案。

1
npx create-react-app my-app

刪除 src 資料夾中的所有檔案,並新增 style.css 檔:

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

新增 index.js 檔:

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
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import './style.css';

// Square 元件接收從父元件傳入的 value 值和 onClick 動作
const Square = (props) => {
return (
<button
className="square"
onClick={props.onClick}
>
{props.value}
</button>
);
}

// Board 元件接收從父元件傳入的 value 值和 onClick 動作
const Board = (props) => {
const renderSquare = (i) => {
return (
<Square
value={props.squares[i]}
onClick={() => props.onClick(i)}
/>
);
}

return (
<div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="board-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="board-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
);
}

const Game = () => {
// 歷史記錄
const [history, setHistory] = useState([
{
squares: Array(9).fill(null),
},
]);

// 步數
const [step, setStep] = useState(0);

// 判斷勝負
const judge = (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;
}

// 作為 onClick 動作的回調,處理點擊後要做的事情
const handleClick = (i) => {
const slice = history.slice(0, step + 1);
const current = slice[slice.length - 1];
const squares = [...current.squares];

if (judge(squares) || squares[i]) {
return;
}

squares[i] = step % 2 ? 'O' : 'X';

setHistory([...slice, { squares }]);
setStep(slice.length);
}

const current = history[step];
const winner = judge(current.squares);
const status = winner
? `Winner: ${winner}`
: `Next player: ${step % 2 ? 'O' : 'X'}`;
const moves = history.map((move, index) => {
return (
<li
key={index}
>
<button
onClick={() => setStep(index)}
>
{`Go to move #${index}`}
</button>
</li>
);
});

return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => { handleClick(i) }}
/>
</div>
<div className="game-info">
<div>
{status}
</div>
<ol>
{moves}
</ol>
</div>
</div>
);
};

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

程式碼

參考資料