React 实现井字棋游戏 (tic-tac-toe) 教程 (6) <译自官方文档>

来源:互联网 发布:实况足球数据网站 编辑:程序博客网 时间:2024/06/06 00:00

React 实现井字棋游戏 (tic-tac-toe) 教程 (1) <译自官方文档>
React 实现井字棋游戏 (tic-tac-toe) 教程 (2) <译自官方文档>
React 实现井字棋游戏 (tic-tac-toe) 教程 (3) <译自官方文档>
React 实现井字棋游戏 (tic-tac-toe) 教程 (4) <译自官方文档>
React 实现井字棋游戏 (tic-tac-toe) 教程 (5) <译自官方文档>

KEYS

当你渲染列表中的项目时,React 总会储存各个项目的相关信息。如果你渲染一个有状态 (state) 的组件,React 需要储存状态。不论你如何实现你的组件,React 总会存储对之前状态的引用。

当你更新列表的时候,React 需要判断到底是哪些内容被更新:你可能添加、删除、重排列,或者更新了项目。

比如,从这样:

code

<li>Alexa: 7 tasks left</li><li>Ben: 5 tasks left</li>

变成这样:

code

<li>Ben: 9 tasks left</li><li>Claudia: 8 tasks left</li><li>Alexa: 5 tasks left</li>

在人眼看来,这只不过就是把 Alexa 和 Ben 调换了下位置,又加上了 Claudia。但 React 只是个程序,它不懂你想要怎么干。因而, React 要求,必须为列表中的每个元素都指定一个 key 属性,即一个字符串,用来区分各个组件。在本案例中,alexaben,claudia就可以是很合适的 key。如果项目对应于数据库中的对象,那数据库 ID 通常也是一个好的选择:

code

<li key={user.id}>{user.name}: {user.taskCount} tasks left</li>

key是 React 保留的特殊属性(和ref一样,那个更高级)。当元素被创建,React 拉取key属性,并将其直接储存到返回的元素上。尽管它看起来像是 props 的一部分,但其实并不能通过this.props.key来引用。在判断哪个子组件应该被更新时,React 自动使用 key。组件自己是没办法查询自己的 key 的。

当列表被重新渲染,React 会在新的版本提取每个元素,寻找里面有没有能和之前列表相匹配上的 key。当一个 key 被添加到集合中时,一个组件实例会被创建;当一个 key 被删除时,一个组件实例会被销毁。React 通过 key 来识别每个组件的身份,组件由此得以在重新渲染的过程中保持状态(state)。如果改变了组件的 key,则该组件实例将被销毁,再重创建一个新的组件实例。这样,原来的状态无法继承,而是创建新的状态。

我们强烈建议,只要你建立动态列表,你应该设置合适的 key。如果你没有合适的 key,或许你该考虑一下重构你的数据,来得到合适的 key。

如果你没有指定任何 key,React 将会发出警告,并回头使用数组的 index 作为 key。但这么干也是不对的,因为当你重新排列表单中的元素,或者 增/删 非列表底部的项目时,就会出问题。明确地传入key={i}虽然会让警告消失,但还是存在相同的问题。所以,大多数情况下,我们也不推荐这么做。

组件的 key 不需要再全局环境下保持唯一,只需要在兄弟组件间保持唯一就可以了。

实现穿越功能

记录历史步骤的列表里头,每一个步骤都有了一个唯一的 ID,即这一步走的时候。在 Game 组件的render方法里,这么添加 key:<li key={move}>,刚才的警告就会消失。

code

const moves = history.map((step, move) => {      const desc = move ?        'Move #' + move :        'Game start';      return (        <li key={move}>          <a href="#" onClick={() => this.jumpTo(move)}>{desc}</a>        </li>      );    });

查看最新的代码

点击里面的步骤,会报错。因为jumpTo方法还没定义。我们在 Game 组件的 state 里面新添加一条,用来指示当前的我们正在查看的步骤。

首先,在 Game 组件的constructor中,添加初始状态:stepNumber: 0

code

class Game extends React.Component {  constructor() {    super();    this.state = {      history: [{        squares: Array(9).fill(null),      }],      stepNumber: 0,      xIsNext: true,    };  }

下一步,在 Game 组件中,定义jumpTo方法,用以更新那条状态。xIsNext同样需要更新,如果一步棋的序号数是偶数,我们就把xIsNext的值设为 true。

向Game的类增加jumpTo方法:

code

  handleClick(i) {    // 本方法不做改动  }  jumpTo(step) {    this.setState({      stepNumber: step,      xIsNext: (step % 2) ? false : true,    });  }  render() {    // 本方法不做改动  }

为了实现在新走一步棋时,stepNumber可以更新的功能,我们在 Game 组件handleClick中的状态更新语句里添加stepNumber: history.length

code

  handleClick(i) {    const history = this.state.history.slice(0, this.state.stepNumber + 1);    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      }]),      stepNumber: history.length,      xIsNext: !this.state.xIsNext,    });  }

现在,你就可以修改 Game 组件的render函数,以实现从历史记录中读取步骤了。

code

render() {    const history = this.state.history;    const current = history[this.state.stepNumber];    const winner = calculateWinner(current.squares);    // the rest has not changed

查看最新的代码

现在你再点击列表里的步骤,棋盘应该就能立即更新,穿越回当时的那一步了。

你可能还想要更新handleClick,以便在读取当前 board 状态的时候,获取stepNumber。这样就能在穿越回去后,又点击棋盘时,创建新的步骤记录。(提示:最简单的办法,就是在handleClick的一开始,用.slice()把历史记录额外的元素切下来。)

圆满完成

现在,你的井字棋已经实现了如下功能:
* 你可以玩井字棋游戏;
* 当有玩家获胜时,宣布结果;
* 存储棋局的历史步骤记录;
* 允许玩家穿越回之前,查看当时棋盘的格局。

干得漂亮!我们希望你已经觉得自己对 React 有了较为深入的把握。

点击查看最终的代码

如果你还有时间,想要练习新学到的技能,这里列出了一些难度提升的改进:

  1. 记录落子的位置时,以“(1,3)”的格式显示,而不仅仅只显示“6”;
  2. 在历史步骤记录表单中,对当前选中的步骤加粗显示;
  3. 重写 Board 组件,使用两个循环来构造小方格,而非直接写死(hardcode)。
  4. 添加切换按钮,实现步骤排列的升序排列或降序排列。
  5. 有人胜出的时候,将那一排胜利的小方格高亮显示。

通过本教程,我们接触了一些 React 的概念,包括:元素 elements, 组件 components, props 和 状态 state。想要进一步深入了解这些话题,请查看其它文档。想要进一步学习如何定义组件,点击React.ComponentAPI引用文档。

译者注

这个仓库是我实现的tic-tac-toe,在文档基础上添加了一些扩展功能:

  • 增加和棋判断;
  • 以直角坐标系的形式(x,y)记录落子位置;
  • 高亮显示胜负手,使结局一目了然;
  • 实现了通过按钮,切换步骤历史记录的正序、逆序排列;
  • 添加了重置按钮,一键重新开始;
  • 高亮显示历史记录列表中的当前选中项;
  • 添加了 AI 功能,可人机对弈,亦可赛艇帮人决策。