React 實現井字棋遊戲 (tic-tac-toe) 教程 (6)

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 功能,可人機對弈,亦可賽艇幫人決策。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章