基础
# 基础
# 创建和嵌套组件
React 应用程序是由 组件 组成的。一个组件是 UI(用户界面)的一部分,它拥有自己的逻辑和外观。组件可以小到一个按钮,也可以大到整个页面。
React 组件是返回标签的 JavaScript 函数:
function MyButton() {
return (
<button>
我是一个按钮
</button>
);
}
export default function MyApp() {
return (
<div>
<h1>欢迎来到我的应用</h1>
<MyButton />
</div>
);
}
React 组件必须以大写字母开头,而 HTML 标签则必须是小写字母。
export default
关键字指定了文件中的主要组件。
# 使用 JSX 编写标签
上面所使用的标签语法被称为 JSX,是可选的但是大多数React项目也会使用
JSX 比 HTML 更加严格,可以留意学习JSX的要求
# 添加样式
以使用 className
来指定一个 CSS 的 class。它与 HTML 的 class
(opens new window) 属性的工作方式相同
<img className="avatar" />
然后,你可以在一个单独的 CSS 文件中为它编写 CSS 规则
React 并没有规定你如何添加 CSS 文件。最简单的方式是使用 HTML 的
<link>
标签。
# 显示数据
JSX 会让你把标签放到 JavaScript 中。而大括号会让你 “回到” JavaScript 中
return (
<h1>
{user.name}
</h1>
);
以将 JSX 属性 “转义到 JavaScript”,但你必须使用大括号 而非 引号
const user = {
name: 'Hedy Lamarr',
imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg',
imageSize: 90,
};
// className="avatar" 是将 "avatar" 字符串传递给 className,作为 CSS 的 class。但 src={user.imageUrl} 会读取 JavaScript 的 user.imageUrl 变量
export default function Profile() {
return (
<>
<h1>{user.name}</h1>
<img
className="avatar"
src={user.imageUrl}
alt={'Photo of ' + user.name}
style={{
width: user.imageSize,
height: user.imageSize
}}
/>
</>
);
}
也可以把更为复杂的表达式放入 JSX 的大括号内,例如 字符串拼接
# 条件渲染
React 没有特殊的语法来编写条件语句,因此你使用的就是普通的 JavaScript 代码
let content;
if (isLoggedIn) {
content = <AdminPanel />;
} else {
content = <LoginForm />;
}
return (
<div>
{content}
</div>
);
<div>
{isLoggedIn ? (
<AdminPanel />
) : (
<LoginForm />
)}
</div>
<div>
{isLoggedIn && <AdminPanel />}
</div>
# 渲染列表
依赖 JavaScript 的特性,例如 for
循环 (opens new window) 和 array 的 map()
函数 (opens new window) 来渲染组件列表
const products = [
{ title: '卷心菜', isFruit: false, id: 1 },
{ title: '大蒜', isFruit: false, id: 2 },
{ title: '苹果', isFruit: true, id: 3 },
];
export default function ShoppingList() {
const listItems = products.map(product =>
<li
key={product.id}
style={{
color: product.isFruit ? 'magenta' : 'darkgreen'
}}
>
{product.title}
</li>
);
return (
<ul>{listItems}</ul>
);
}
# 响应事件
你可以通过在组件中声明 事件处理 函数来响应事件:
function MyButton(){
function handleClick(){
alert('You clicked me!');
}
return {
<button onClick={handleClick}>
点我
</button>
}
}
注意,
onClick={handleClick}
的结尾没有小括号!不要 调用 事件处理函数:你只需 把函数传递给事件 即可。当用户点击按钮时 React 会调用你传递的事件处理函数。
# 更新界面
状态管理,通常你会希望你的组件 “记住” 一些信息并展示出来,比如一个按钮被点击的次数。要做到这一点,你需要在你的组件中添加 state
首先,从 React 引入 useState
(opens new window):
import { useState } from 'react';
以在你的组件中声明一个 state 变量:
function MyButton() {
const [count, setCount] = useState(0);
// ...
你将从 useState
中获得两样东西:当前的 state(count
),以及用于更新它的函数(setCount
)。你可以给它们起任何名字,但按照惯例会像 [something, setSomething]
这样为它们命名。
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
//当你想改变 state 时,调用 setCount() 并将新的值传递给它
setCount(count + 1);
}
return (
<button onClick={handleClick}>
Clicked {count} times
</button>
);
}
# 使用 Hook
以 use
开头的函数被称为 Hook。useState
是 React 提供的一个内置 Hook。
可以在 React API 参考 (opens new window) 中找到其他内置的 Hook。你也可以通过组合现有的 Hook 来编写属于你自己的 Hook。
Hook 比普通函数更为严格。你只能在你的组件(或其他 Hook)的 顶层 调用 Hook。如果你想在一个条件或循环中使用 useState
,请提取一个新的组件并在组件内部使用它。
# 组件间共享数据
我们通常需要组件间共享一份数据
import { useState } from 'react';
export default function MyApp() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<div>
<h1>共同更新的计数器</h1>
<MyButton count={count} onClick={handleClick} />
<MyButton count={count} onClick={handleClick} />
</div>
);
}
function MyButton({ count, onClick }) {
return (
<button onClick={onClick}>
点了 {count} 次
</button>
);
}
使用这种方式传递的信息被称作 prop。此时 MyApp
组件包含了 count
state 以及 handleClick
事件处理函数,并将它们作为 prop 传递给 了每个按钮。
# 开发
# 安装
安装Node.js
创建react应用
npx create-react-app my-react-app
cd my-react-app
npm start
# 井字棋项目
创建好了项目之后
# 来看App.js
App.js
的代码创建了一个 组件。在 React 中,组件是一段可重用代码,它通常作为 UI 界面的一部分。
export default function Square() {
// 命名了一个函数,default 关键字表明它是文件中的主要函数。
return <button className="square">X</button>
//JavaScript 的 return 关键字意味着后面的内容都作为值返回给函数的调用者。<button> 是一个 JSX 元素。
}
# 构建棋盘
想要创建多个按钮
export default function Square() {
return <button className="square">X</button><button className="square">X</button>;
}
会报错
React 组件必须返回单个 JSX 元素,不能像两个按钮那样返回多个相邻的 JSX 元素。可以使用 Fragment(<>
与 </>
)包裹多个相邻的 JSX 元素,如下所示:
export default function Square() {
return (
<>
<button className="square">X</button>
<button className="square">X</button>
</>
);
}
最后需要9个方块
export default function Board() {//名称更改为 Board
return (
<>
<div className="board-row">
<button className="square">1</button>
<button className="square">2</button>
<button className="square">3</button>
</div>
<div className="board-row">
<button className="square">4</button>
<button className="square">5</button>
<button className="square">6</button>
</div>
<div className="board-row">
<button className="square">7</button>
<button className="square">8</button>
<button className="square">9</button>
</div>
</>
);
}
但是样式还没渲染好,加上样式
.board-row {
display: flex;
justify-content: center;
margin: 5px 0;
}
.square {
width: 60px;
height: 60px;
font-size: 24px;
font-weight: bold;
color: #333;
background-color: #f9f9f9;
border: 2px solid #ddd;
border-radius: 5px;
cursor: pointer;
transition: all 0.2s ease;
}
.square:hover {
background-color: #e6f7ff;
border-color: #1890ff;
}
.square:active {
background-color: #d6e4ff;
border-color: #2b8aef;
transform: scale(0.95);
}
.board {
display: flex;
flex-direction: column;
align-items: center;
margin: 20px auto;
padding: 20px;
background-color: #fff;
border: 2px solid #ccc;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 36px;
color: #333;
text-align: center;
margin-bottom: 20px;
}
# 通过props传递数据
当用户单击方块时,我们要将方块的值从空更改为“X”,这样会不会是使用一个复用的组件提供他们调用就好了呢?
React 的组件架构可以创建可重用的组件,以避免混乱、重复的代码。
如下面的代码:
function Square({ value }) {
return <button className="square">{value}</button>
}
export default function Board() {
return (
<>
<div className="board-row">
<Square value="1" />
<Square value="2" />
<Square value="3" />
</div>
<div className="board-row">
<Square value="4" />
<Square value="5" />
<Square value="6" />
</div>
<div className="board-row">
<Square value="7" />
<Square value="8" />
<Square value="9" />
</div>
</>
)
}
# 创建一个具有交互性的组件
当你单击它的时候,Square
组件需要显示“X”。在 Square
内部声明一个名为 handleClick
的函数。然后,将 onClick
添加到由 Square
返回的 JSX 元素的 button 的 props 中:
function Square({ value }) {
function handleClick() {
console.log('clicked!');
}
return (
<button
className="square"
onClick={handleClick}
>
{value}
</button>
);
}
我们希望 Square 组件能够“记住”它被单击过,并用“X”填充它。为了“记住”一些东西,组件使用 state。
使用后如下:
import { useState } from 'react';
function Square() {
const [value, setValue] = useState(null);
function handleClick() {
setValue('X');
}
return (
<button
className="square"
onClick={handleClick}
>
{value}
</button>
);
}
export default function Board() {
return (
<>
<div className="board-row">
<Square />
<Square />
<Square />
</div>
<div className="board-row">
<Square />
<Square />
<Square />
</div>
<div className="board-row">
<Square />
<Square />
<Square />
</div>
</>
);
}
过了一下基础的知识之后,我们就要:
# 完成这个游戏!!
我们现在有了棋盘的基本模块了,要实现完整的游戏:在需要在棋盘上交替放置“X”和“O”,并且需要一种确定获胜者的方法
# 状态提升
目前,每个 Square
组件都维护着游戏 state 的一部分。要检查井字棋游戏中的赢家,Board
需要以某种方式知道 9 个 Square
组件中每个组件的 state。
要从多个子组件收集数据,或让两个子组件相互通信,请改为在其父组件中声明共享 state。父组件可以通过 props 将该 state 传回给子组件。这使子组件彼此同步并与其父组件保持同步。
重构 React 组件时,将状态提升到父组件中
import { useState } from 'react';
function Square({ value }) {
return <button className="square">{value}</button>;
}
export default function Board() {
const [squares, setSquares] = useState(Array(9).fill(null));
return (
<>
<div className="board-row">
<Square value={squares[0]} />
<Square value={squares[1]} />
<Square value={squares[2]} />
</div>
<div className="board-row">
<Square value={squares[3]} />
<Square value={squares[4]} />
<Square value={squares[5]} />
</div>
<div className="board-row">
<Square value={squares[6]} />
<Square value={squares[7]} />
<Square value={squares[8]} />
</div>
</>
);
}
在,每个 Square 都会收到一个 value
props,对于空方块,该 props 将是 'X'
、'O'
或 null
。
下面创建点击事件,点击方块实现赋值
调用函数
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
JavaScript 支持 闭包 (opens new window),这意味着内部函数(例如
handleClick
)可以访问外部函数(例如Board
)中定义的变量和函数。handleClick
函数可以读取squares
state 并调用setSquares
方法,因为它们都是在Board
函数内部定义的。
import { useState } from 'react';
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
export default function Board() {
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(i) {
const nextSquares = squares.slice(); //调用了 .slice() 来创建 squares 数组的副本
nextSquares[i] = 'X';
setSquares(nextSquares);
}
return (
<>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
);
}
现在,我们在 Board
组件中处理 state, Board
父组件将 props 传递给 Square
子组件,以便它们可以正确显示。
# 交替落子
每次玩家落子时,xIsNext
(一个布尔值)将被翻转以确定下一个玩家,游戏 state 将被保存。你将更新 Board
的 handleClick
函数以翻转 xIsNext
的值:
export default function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(i) {
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = "X";
} else {
nextSquares[i] = "O";
}
setSquares(nextSquares);
setXIsNext(!xIsNext);
}
return (
//...
);
}
要注意方块的覆盖问题 就是要做一个判断
完整代码如下:
import { useState } from 'react';
function Square({value, onSquareClick}) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
export default function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(i) {
if (squares[i]) {
return;
}
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = 'X';
} else {
nextSquares[i] = 'O';
}
setSquares(nextSquares);
setXIsNext(!xIsNext);
}
return (
<>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
);
}
# 宣布获胜者
现在你可以轮流对战了,接下来我们应该显示游戏何时获胜。为此,你将添加一个名为 calculateWinner
的辅助函数用来判断是否获胜
export default function Board() {
//...
}
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;
}
通过将辅助函数放在最后
还有加上让执行这个函数以及显示玩家获胜的信息,完整代码如下:
import { useState } from 'react';
function Square({value, onSquareClick}) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
export default function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(i) {
if (calculateWinner(squares) || squares[i]) {
return;
}
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = 'X';
} else {
nextSquares[i] = 'O';
}
setSquares(nextSquares);
setXIsNext(!xIsNext);
}
const winner = calculateWinner(squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}
return (
<>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
);
}
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;
}
# 添加时间旅行
作为最后的练习,让我们能够“回到”到游戏中之前的动作。
# 存储落子历史
之前创造的数组的副本就是为了时间旅行做准备的
所以创造一个history
的数组
# 再一次“状态提升”
现在将编写一个名为 Game
的新顶级组件来显示过去的着法列表。这就是放置包含整个游戏历史的 history
state 的地方。
将 history
state 放入 Game
组件将使你可以从其 Board
子组件中删除 squares
state。
首先,添加一个带有 export default
的 Game
组件。让它渲染 Board
组件和一些标签:
function Board() {
// ...
}
export default function Game() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<ol>{/*TODO*/}</ol>
</div>
</div>
);
}
向 Game
组件添加一些 state 以跟踪下一个玩家和落子历史:
export default function Game() {
const [xIsNext, setXIsNext] = useState(true);
const [history, setHistory] = useState([Array(9).fill(null)]);
const currentSquares = history[history.length - 1];
// ...
接下来,在 Game
组件中创建一个 handlePlay
函数,Board
组件将调用该函数来更新游戏。将 xIsNext
、currentSquares
和 handlePlay
作为 props 传递给 Board
组件:
export default function Game() {
const [xIsNext, setXIsNext] = useState(true);
const [history, setHistory] = useState([Array(9).fill(null)]);
const currentSquares = history[history.length - 1];
function handlePlay(nextSquares) {
// TODO
}
return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
//...
)
}
完整代码:
import { useState } from 'react'
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
)
}
function Board({ xIsNext, squares, onPlay }) {
function handleClick(i) {
if (calculateWinner(squares) || squares[i]) {
return
}
const nextSquares = squares.slice()
if (xIsNext) {
nextSquares[i] = 'X'
} else {
nextSquares[i] = 'O'
}
onPlay(nextSquares)
}
const winner = calculateWinner(squares)
let status
if (winner) {
status = 'Winner: ' + winner
} else {
status = 'Next player: ' + (xIsNext ? 'X' : 'O')
}
return (
<>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
)
}
export default function Game() {
const [xIsNext, setXIsNext] = useState(true)
const [history, setHistory] = useState([Array(9).fill(null)])
const currentSquares = history[history.length - 1]
function handlePlay(nextSquares) {
setHistory([...history, nextSquares])
setXIsNext(!xIsNext)
}
return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
</div>
<div className="game-info">
<ol>{/*TODO*/}</ol>
</div>
</div>
)
}
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
}
# 显示过去的落子
import { useState } from 'react';
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
function Board({ xIsNext, squares, onPlay }) {
function handleClick(i) {
if (calculateWinner(squares) || squares[i]) {
return;
}
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = 'X';
} else {
nextSquares[i] = 'O';
}
onPlay(nextSquares);
}
const winner = calculateWinner(squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}
return (
<>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
);
}
export default function Game() {
const [xIsNext, setXIsNext] = useState(true);
const [history, setHistory] = useState([Array(9).fill(null)]);
const currentSquares = history[history.length - 1];
function handlePlay(nextSquares) {
setHistory([...history, nextSquares]);
setXIsNext(!xIsNext);
}
function jumpTo(nextMove) {
// TODO
}
const moves = history.map((squares, move) => {
let description;
if (move > 0) {
description = 'Go to move #' + move;
} else {
description = 'Go to game start';
}
return (
<li>
<button onClick={() => jumpTo(move)}>{description}</button>
</li>
);
});
return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
</div>
<div className="game-info">
<ol>{moves}</ol>
</div>
</div>
);
}
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;
}
# 实现时间旅行
import { useState } from 'react';
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
function Board({ xIsNext, squares, onPlay }) {
function handleClick(i) {
if (calculateWinner(squares) || squares[i]) {
return;
}
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = 'X';
} else {
nextSquares[i] = 'O';
}
onPlay(nextSquares);
}
const winner = calculateWinner(squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}
return (
<>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
);
}
export default function Game() {
const [history, setHistory] = useState([Array(9).fill(null)]);
const [currentMove, setCurrentMove] = useState(0);
const xIsNext = currentMove % 2 === 0;
const currentSquares = history[currentMove];
function handlePlay(nextSquares) {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length - 1);
}
function jumpTo(nextMove) {
setCurrentMove(nextMove);
}
const moves = history.map((squares, move) => {
let description;
if (move > 0) {
description = 'Go to move #' + move;
} else {
description = 'Go to game start';
}
return (
<li key={move}>
<button onClick={() => jumpTo(move)}>{description}</button>
</li>
);
});
return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
</div>
<div className="game-info">
<ol>{moves}</ol>
</div>
</div>
);
}
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;
}
最后再来个加强版的如下: