React 初学者: 教程:React 简介 五
什么是React?
React 是一个用于构建用户界面的声明式、高效且灵活的 JavaScript 库。它使您可以从称为“组件”的小而孤立的代码组成复杂的 UI。
React 有几种不同类型的组件,但我们将从React.Component
子类开始:
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" />
我们很快就会谈到有趣的类似 XML 的标签。我们使用组件来告诉 React 我们想在屏幕上看到什么。当我们的数据发生变化时,React 将有效地更新和重新渲染我们的组件。
在这里, ShoppingList 是一个React 组件类,或React 组件类型。组件接受参数,称为props
(“属性”的缩写),并返回视图层次结构以通过该render
方法显示。
该render
方法返回您想在屏幕上看到的内容的_描述。_React 接受描述并显示结果。特别是,render
返回一个React 元素,它是对要呈现的内容的轻量级描述。大多数 React 开发人员使用一种称为“JSX”的特殊语法,这使得这些结构更容易编写。语法在<div />
构建时转换为React.createElement('div')
. 上面的例子等价于:
return React.createElement('div', {className: 'shopping-list'},
React.createElement('h1', /* ... h1 children ... */),
React.createElement('ul', /* ... ul children ... */)
);
如果您好奇,API 参考createElement()
中有更详细的描述,但我们不会在本教程中使用它。相反,我们将继续使用 JSX。
JSX 具有 JavaScript 的全部功能。您可以将_任何_JavaScript 表达式放在 JSX 中的大括号内。每个 React 元素都是一个 JavaScript 对象,您可以将其存储在变量中或在程序中传递。
上面的ShoppingList
组件只渲染内置的 DOM 组件,例如<div />
和<li />
。但是你也可以编写和渲染自定义的 React 组件。例如,我们现在可以通过编写来引用整个购物清单<ShoppingList />
。每个 React 组件都经过封装,可以独立运行;这允许您从简单的组件构建复杂的 UI。
检查入门代码
如果您要在浏览器中处理本教程,请在新选项卡中打开此代码:Starter Code。如果您要在本地处理本教程,请在您的项目文件夹中打开(在设置src/index.js
过程中您已经接触过此文件)。
此入门代码是我们正在构建的基础。我们提供了 CSS 样式,因此您只需专注于学习 React 和编程井字游戏。
通过检查代码,你会注意到我们有三个 React 组件:
- 正方形
- 木板
- 游戏
Square 组件渲染单个<button>
,Board 渲染 9 个正方形。Game 组件使用占位符值渲染一个棋盘,稍后我们将对其进行修改。当前没有交互式组件。
通过道具传递数据
为了让我们的脚湿透,让我们尝试将一些数据从我们的 Board 组件传递到我们的 Square 组件。
我们强烈建议您在学习本教程时手动输入代码,而不是使用复制/粘贴。这将帮助您发展肌肉记忆和更强的理解力。
在 Board 的renderSquare
方法中,更改代码以将调用的道具传递value
给 Square:
class Board extends React.Component {
renderSquare(i) {
return <Square value={i} />; }
}
更改 Square 的方法以通过替换render
来显示该值:{/* TODO */}
{this.props.value}
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.value} </button>
);
}
}
前:
之后:您应该在渲染输出的每个方块中看到一个数字。
恭喜!您刚刚从父 Board 组件“传递了一个 prop”到子 Square 组件。传递 props 是 React 应用程序中信息从父母到孩子的流动方式。
制作交互式组件
当我们单击它时,让我们用“X”填充 Square 组件。首先,将从 Square 组件的render()
函数返回的按钮标签更改为:
class Square extends React.Component {
render() {
return (
<button className="square" onClick={function() { console.log('click'); }}> {this.props.value}
</button>
);
}
}
如果您现在单击 Square,您应该会在浏览器的开发工具控制台中看到“单击”。
笔记
为了节省输入并避免 的混淆行为
this
,我们将在此处和以下进一步使用事件处理程序的箭头函数语法: >class Square extends React.Component { render() { return ( <button className="square" onClick={() => console.log('click')}> {this.props.value} </button> ); } }
请注意
onClick={() => console.log('click')}
,我们如何将_函数_作为onClick
道具传递。React 只会在点击后调用这个函数。忘记() =>
和写入onClick={console.log('click')}
是一个常见的错误,并且会在每次组件重新渲染时触发。下一步,我们希望 Square 组件“记住”它被点击过,并用“X”标记填充它。为了“记住”事物,组件使用state。
React 组件可以通过
this.state
在其构造函数中设置来获得状态。this.state
应该被认为是其定义的 React 组件的私有。让我们将 Square 的当前值存储在 中this.state
,并在单击 Square 时更改它。首先,我们将在类中添加一个构造函数来初始化状态:
class Square extends React.Component { constructor(props) { super(props); this.state = { value: null, }; } render() { return ( <button className="square" onClick={() => console.log('click')}> {this.props.value} </button> ); } }
笔记
在JavaScript 类中,您需要
super
在定义子类的构造函数时始终调用。所有具有 a 的 React 组件类都constructor
应该以super(props)
调用开头。现在我们将更改 Square 的
render
方法以在单击时显示当前状态的值:
- 替换
this.props.value
为标签this.state.value
内。<button>
- 将
onClick={...}
事件处理程序替换为onClick={() => this.setState({value: 'X'})}
.- 将
className
和onClick
道具放在不同的行上以提高可读性。在这些更改之后,
<button>
Square 的render
方法返回的标签如下所示:``` class Square extends React.Component { constructor(props) { super(props); this.state = { value: null, }; }
render() { return ( ); } }
this.setState通过从
onClickSquare 方法中的处理程序调用
render,我们告诉 React 在单击 Square 时重新渲染它
当你调用setState
一个组件时,React 也会自动更新其中的子组件。
开发者工具
适用于Chrome和Firefox的 React Devtools 扩展允许您使用浏览器的开发人员工具检查 React 组件树。
React DevTools 可让您检查 React 组件的 props 和状态。
安装 React DevTools 后,您可以右键单击页面上的任何元素,单击“Inspect”打开开发者工具,React 选项卡(“⚛️ Components”和“⚛️ Profiler”)将作为最后一个选项卡出现在对。使用“⚛️ Components”检查组件树。
但是,请注意,还有一些额外的步骤可以让它与 CodePen 一起使用:
- 登录或注册并确认您的电子邮件(需要防止垃圾邮件)。
- 单击“分叉”按钮。
- 单击“更改视图”,然后选择“调试模式”。
- 在打开的新选项卡中,devtools 现在应该有一个 React 选项卡。
完成游戏
我们现在有了井字游戏的基本构建块。为了有一个完整的游戏,我们现在需要在棋盘上交替放置“X”和“O”,我们需要一种方法来确定获胜者。
提升状态
目前,每个 Square 组件都维护着游戏的状态。为了检查获胜者,我们将在一个位置保留 9 个方格中每个方格的值。
我们可能认为董事会应该只向每个 Square 询问 Square 的状态。尽管这种方法在 React 中是可行的,但我们不鼓励它,因为代码变得难以理解、容易出现错误并且难以重构。相反,最好的方法是将游戏的状态存储在父 Board 组件中,而不是存储在每个 Square 中。Board 组件可以通过传递一个 prop 来告诉每个 Square 要显示什么,就像我们向每个 Square 传递一个数字时所做的那样。
要从多个子组件收集数据,或让两个子组件相互通信,您需要在其父组件中声明共享状态。父组件可以使用 props 将状态向下传递给子组件;这使子组件相互之间以及与父组件保持同步。
当 React 组件被重构时,将状态提升到父组件中是很常见的——让我们借此机会尝试一下。
向 Board 添加一个构造函数,并将 Board 的初始状态设置为包含对应于 9 个正方形的 9 个空数组:
class Board extends React.Component {
constructor(props) { super(props); this.state = { squares: Array(9).fill(null), }; }
renderSquare(i) {
return <Square value={i} />;
}
当我们稍后填写板时,this.state.squares
数组将如下所示:
[
'O', null, 'X',
'X', 'X', 'O',
'O', null, null,
]
Board 的renderSquare
方法目前如下所示:
renderSquare(i) {
return <Square value={i} />;
}
一开始,我们将道具从 Board 向下传递,value
以在每个 Square 中显示从 0 到 8 的数字。在之前的不同步骤中,我们将数字替换为由 Square 自身状态确定的“X”标记。这就是为什么 Square 目前忽略value
了董事会传递给它的道具。
我们现在将再次使用道具传递机制。我们将修改 Board 以指示每个单独的 Square 关于其当前值('X'
、'O'
或null
)。我们已经squares
在 Board 的构造函数中定义了数组,我们将修改 Board 的renderSquare
方法以从中读取:
renderSquare(i) {
return <Square value={this.state.squares[i]} />; }
现在,每个 Square 都会收到一个value
prop,它可以是'X'
、'O'
或null
空方格。
接下来,我们需要更改单击 Square 时发生的情况。Board 组件现在维护填充了哪些方格。我们需要为 Square 创建一种更新 Board 状态的方法。由于 state 被认为是定义它的组件私有的,我们不能直接从 Square 更新 Board 的 state。
相反,我们将一个函数从 Board 传递给 Square,当单击一个正方形时,我们将让 Square 调用该函数。我们将renderSquare
Board 中的方法更改为:
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)} />
);
}
笔记
为了便于阅读,我们将返回的元素分成多行,并添加了括号,这样 JavaScript 就不会在后面插入分号
return
并破坏我们的代码。现在我们将两个道具从 Board 传递到 Square:
value
和onClick
.onClick
prop 是 Square 在单击时可以调用的函数。我们将对 Square 进行以下更改:
- 在 Square 的方法中替换
this.state.value
为this.props.value
render
- 在 Square 的方法中替换
this.setState()
为this.props.onClick()
render
- 从 Square中删除,
constructor
因为 Square 不再跟踪游戏的状态在这些更改之后,Square 组件如下所示:
class Square extends React.Component { render() { return ( <button className="square" onClick={() => this.props.onClick()} > {this.props.value} </button> ); } }
单击 Square 时,
onClick
会调用 Board 提供的函数。以下是如何实现这一点的回顾:
2. 单击按钮时,React 将调用Square方法```onClick```中定义的事件处理程序。```render()``` 3. 此事件处理程序调用```this.props.onClick()```. Square 的```onClick```道具由董事会指定。 4. 由于 Board 传递```onClick={() => this.handleClick(i)}```给 Square,Square 会```handleClick(i)```在单击时调用 Board。 5. 我们还没有定义```handleClick()```方法,所以我们的代码崩溃了。如果你现在点击一个方块,你应该会看到一个红色的错误屏幕,上面写着“this.handleClick is not a function”。 > 笔记 > > DOM```<button>```元素的```onClick```属性对 React 具有特殊的意义,因为它是一个内置组件。对于像 Square 这样的自定义组件,命名由您决定。我们可以给 Square 的```onClick```prop 或 Board 的```handleClick```方法起任何名字,代码也一样。在 React 中,通常为```on[Event]```表示事件的 props 和```handle[Event]```处理事件的方法使用名称。 当我们试图点击一个 Square 时,我们应该得到一个错误,因为我们还没有定义```handleClick```。我们现在将添加```handleClick```到 Board 类:
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 (
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(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
} }
**[此时查看完整代码](https://zshipu.com/t?url=https://codepen.io/gaearon/pen/ybbQJX?editors=0010)**
在这些更改之后,我们再次能够单击方块来填充它们,就像我们以前一样。但是,现在状态存储在 Board 组件中,而不是单独的 Square 组件中。当 Board 的状态发生变化时,Square 组件会自动重新渲染。保持 Board 组件中所有方格的状态将使其能够确定未来的获胜者。
由于 Square 组件不再保持状态,因此 Square 组件从 Board 组件接收值并在单击时通知 Board 组件。在 React 术语中,Square 组件现在是**受控组件**。董事会对其拥有完全控制权。
请注意如何在 中```handleClick```,我们调用```.slice()```创建```squares```要修改的数组的副本,而不是修改现有数组。```squares```我们将在下一节解释为什么要创建数组的副本。
### 为什么不变性很重要
在前面的代码示例中,我们建议您```squares```使用该```slice()```方法创建数组的副本,而不是修改现有数组。我们现在将讨论不变性以及为什么学习不变性很重要。
更改数据通常有两种方法。第一种方法是通过直接_更改_数据的值来改变数据。第二种方法是用具有所需更改的新副本替换数据。
#### 突变的数据变化
var player = {score: 1, name: ‘Jeff’}; player.score = 2; // Now player is {score: 2, name: ‘Jeff’}
#### 无突变的数据更改
var player = {score: 1, name: ‘Jeff’};
var newPlayer = Object.assign({}, player, {score: 2}); // Now player is unchanged, but newPlayer is {score: 2, name: ‘Jeff’}
// Or if you are using object spread syntax proposal, you can write: // var newPlayer = {…player, score: 2};
最终结果是相同的,但通过不直接改变(或更改基础数据),我们获得了如下所述的几个好处。
#### 复杂的功能变得简单
不变性使复杂的功能更容易实现。在本教程的后面,我们将实现一个“时间旅行”功能,它允许我们回顾井字游戏的历史并“跳回”到之前的动作。此功能并非特定于游戏 - 撤消和重做某些操作的能力是应用程序中的常见要求。避免直接的数据突变让我们可以保持游戏历史的先前版本完整,并在以后重用它们。
#### 检测变化
检测可变对象的变化很困难,因为它们是直接修改的。这种检测需要将可变对象与其自身的先前副本进行比较,并需要遍历整个对象树。
检测不可变对象的变化要容易得多。如果被引用的不可变对象与前一个不同,则该对象已更改。
#### 确定何时在 React 中重新渲染
不变性的主要好处是它可以帮助您在 React 中构建_纯组件。_不可变数据可以轻松确定是否进行了更改,这有助于确定组件何时需要重新渲染。
您可以通过阅读[Optimizing Performance](https://zshipu.com/t?url=https://reactjs.org/docs/optimizing-performance.html#examples)了解更多关于```shouldComponentUpdate()```以及如何构建_纯组件_的信息。
### 功能组件
我们现在将 Square 更改为**函数组件**。
在 React 中,**函数组件**是一种更简单的编写组件的方法,它只包含一个```render```方法并且没有自己的状态。```React.Component```我们可以编写一个函数,将```props```其作为输入并返回应该呈现的内容,而不是定义一个扩展的类。函数组件写起来没有类那么繁琐,很多组件都可以这样表达。
用这个函数替换 Square 类:
function Square(props) { return ( ); }
我们已经改变```this.props```了```props```它出现的两次。
**[此时查看完整代码](https://zshipu.com/t?url=https://codepen.io/gaearon/pen/QvvJOv?editors=0010)**
> 笔记
>
> 当我们将 Square 修改为函数组件时,我们也改成```onClick={() => this.props.onClick()}```了更短的(注意_两边_```onClick={props.onClick}```没有括号)。
### 轮流
我们现在需要修复井字游戏中的一个明显缺陷:无法在棋盘上标记“O”。
默认情况下,我们将第一步设置为“X”。我们可以通过修改 Board 构造函数中的初始状态来设置此默认值:
class Board extends React.Component { constructor(props) { super(props); this.state = { squares: Array(9).fill(null), xIsNext: true, }; }
每次玩家移动时,```xIsNext```(布尔值)将被翻转以确定下一个玩家并保存游戏状态。我们将更新 Board 的```handleClick```函数以翻转 的值```xIsNext```:
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? ‘X’ : ‘O’; this.setState({ squares: squares, xIsNext: !this.state.xIsNext, }); }
通过这种变化,“X”和“O”可以轮流使用。尝试一下!
让我们还更改 Board 中的“状态”文本,```render```以便显示下一个回合的玩家:
render() {
const status = ‘Next player: ‘ + (this.state.xIsNext ? ‘X’ : ‘O’); return ( // the rest has not changed
应用这些更改后,您应该拥有此 Board 组件:
class Board extends React.Component { constructor(props) { super(props); this.state = { squares: Array(9).fill(null), xIsNext: true, }; }
handleClick(i) { const squares = this.state.squares.slice(); squares[i] = this.state.xIsNext ? ‘X’ : ‘O’; this.setState({ squares: squares, xIsNext: !this.state.xIsNext, }); }
renderSquare(i) {
return (
render() { const status = ‘Next player: ‘ + (this.state.xIsNext ? ‘X’ : ‘O’); return (
**[此时查看完整代码](https://zshipu.com/t?url=https://codepen.io/gaearon/pen/KmmrBy?editors=0010)**
### 宣布获胜者
既然我们显示了下一个回合是哪个玩家,我们还应该显示游戏何时获胜并且没有更多回合可做。复制此辅助函数并将其粘贴到文件末尾:
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; }
给定一个由 9 个方格组成的数组,此函数将检查获胜者并根据需要返回```'X'```、```'O'```或```null```。
我们将调用```calculateWinner(squares)```Board 的```render```函数来检查玩家是否获胜。如果玩家赢了,我们可以显示诸如“Winner: X”或“Winner: O”之类的文本。我们将使用以下代码替换```status```Board```render```函数中的声明:
render() {
const winner = calculateWinner(this.state.squares); let status; if (winner) { status = ‘Winner: ‘ + winner; } else { status = ‘Next player: ‘ + (this.state.xIsNext ? ‘X’ : ‘O’); } return ( // the rest has not changed
handleClick```如果有人赢了游戏或者广场已经被填满,我们现在可以通过忽略点击来更改 Board 的功能以提前返回:
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
恭喜!您现在有一个有效的井字游戏。而且你也刚刚学习了 React 的基础知识。所以_你_可能是这里真正的赢家。
添加时间旅行
作为最后的练习,让我们可以“回到过去”到游戏中的先前动作。
存储移动历史
如果我们改变squares
数组,实现时间旅行将非常困难。
但是,我们习惯在每次移动后创建一个新的数组slice()
副本,并将其视为不可变的。这将允许我们存储数组的每个过去版本,并在已经发生的转弯之间导航。squares
squares
我们会将过去的squares
数组存储在另一个名为history
. 该history
数组代表所有棋盘状态,从第一次移动到最后一次移动,并具有如下形状:
history = [
// Before first move
{
squares: [
null, null, null,
null, null, null,
null, null, null,
]
},
// After first move
{
squares: [
null, null, null,
null, 'X', null,
null, null, null,
]
},
// After second move
{
squares: [
null, null, null,
null, 'X', null,
null, null, 'O',
]
},
// ...
]
现在我们需要决定哪个组件应该拥有history
状态。
再次提升状态
我们希望顶级 Game 组件显示过去移动的列表。它需要访问 来history
做到这一点,所以我们将把history
状态放在顶级 Game 组件中。
将状态放入 Game 组件中可以让我们从其子 Board 组件history
中移除状态。squares
就像我们将状态从 Square 组件“提升”到 Board 组件一样,我们现在将它从 Board 提升到顶级 Game 组件。这使 Game 组件可以完全控制 Board 的数据,并让它指示 Board 从history
.
首先,我们将在其构造函数中设置 Game 组件的初始状态:
class Game extends React.Component {
constructor(props) { super(props); this.state = { history: [{ squares: Array(9).fill(null), }], xIsNext: true, }; }
render() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
<div className="game-info">
<div>{/* status */}</div>
<ol>{/* TODO */}</ol>
</div>
</div>
);
}
}
接下来,我们将让 Board 组件接收来自 Game 组件的 props squares
。onClick
由于我们现在在 Board 中有一个用于许多 Squares 的单击处理程序,我们需要将每个 Square 的位置传递给onClick
处理程序以指示单击了哪个 Square。以下是转换 Board 组件所需的步骤:
- 删除
constructor
in Board。 - 替换
this.state.squares[i]
为this.props.squares[i]
板中的renderSquare
. - 替换
this.handleClick(i)
为this.props.onClick(i)
板中的renderSquare
.
Board 组件现在看起来像这样:
class Board extends React.Component {
handleClick(i) {
const squares = this.state.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
renderSquare(i) {
return (
<Square
value={this.props.squares[i]} onClick={() => this.props.onClick(i)} />
);
}
render() {
const winner = calculateWinner(this.state.squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
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(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
我们将更新 Game 组件的render
函数以使用最近的历史记录来确定和显示游戏的状态:
render() {
const history = this.state.history; const current = history[history.length - 1]; const winner = calculateWinner(current.squares); let status; if (winner) { status = 'Winner: ' + winner; } else { status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); }
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>{/* TODO */}</ol>
</div>
</div>
);
}
由于 Game 组件现在正在渲染游戏的状态,我们可以从 Board 的render
方法中删除相应的代码。重构后,Board 的render
功能如下:
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>
);
}
最后,我们需要将handleClick
方法从 Board 组件移动到 Game 组件。我们还需要修改handleClick
,因为 Game 组件的状态结构不同。在 Game 的handleClick
方法中,我们将新的历史条目连接到history
.
handleClick(i) {
const history = this.state.history; const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
history: history.concat([{ squares: squares, }]), xIsNext: !this.state.xIsNext,
});
}
笔记
此时,Board 组件只需要```renderSquare```and```render```方法。游戏的状态和```handleClick```方法应该在 Game 组件中。 **[此时查看完整代码](https://zshipu.com/t?url=https://codepen.io/gaearon/pen/EmmOqJ?editors=0010)** ### 显示过去的动作 由于我们正在记录井字游戏的历史,我们现在可以将其作为过去移动的列表显示给玩家。 我们之前了解到 React 元素是一流的 JavaScript 对象。我们可以在我们的应用程序中传递它们。要在 React 中渲染多个项目,我们可以使用 React 元素数组。 在 JavaScript 中,数组有一个通常用于将数据映射到其他数据的[```map()```方法,例如:](https://zshipu.com/t?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
const numbers = [1, 2, 3]; const doubled = numbers.map(x => x * 2); // [2, 4, 6]
使用该```map```方法,我们可以将我们的移动历史映射到表示屏幕上按钮的 React 元素,并显示一个按钮列表以“跳转”到过去的移动。 让我们```map```结束```history```游戏中的```render```方法:
render() { const history = this.state.history; const current = history[history.length - 1]; const winner = calculateWinner(current.squares);
const moves = history.map((step, move) => { const desc = move ? ‘Go to move #’ + move : ‘Go to game start’; return (
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>
- {moves}