Reactチュートリアル三目並べをやってみた

# はじめに
チュートリアルを通してReactの基本的な処理を学ぶのが目的です。

# 導入

ローカル環境で進めたかったので、下記のチュートリアルに書いてある通りに進める

1. [Node.js](https://nodejs.org/en/) をインストール
2. さきほど開いた CodeSandbox のタブで、左上隅のボタンを押してメニューを開き、そのメニューで **Download Sandbox** を選択して、ファイルをローカルにアーカイブとしてダウンロード
3. アーカイブを解凍し、ターミナルを開いて解凍したディレクトリに `cd` する
4. `npm install` で依存ライブラリをインストール
5. `npm start` でローカルサーバを起動し、プロンプト通りに操作し、ブラウザで実行されるコードを確認する

- 下記が表示されれば環境構築完了！

![スクリーンショット 2024-03-22 6.31.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/7613f05d-a0b9-5e47-0554-7721d1bb252f.png)

- 各ファイルをみてみる


## App.js
コンポーネントを作成

```App.js
export default function Square() {
  return <button className="square">X</button>;
}
```

## styles.css
React アプリのスタイルが定義されている

## index.js
App.js ファイルで作成したコンポーネントと Web ブラウザとの橋渡しの役割

```index.js
import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./styles.css";

import App from "./App";

const root = createRoot(document.getElementById("root"));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
```

## マスを作成する

- React コンポーネントは、JSX 要素を複数隣り合わせて返すのではなく、単一の JSX 要素を返す。複数の並ぶJSX 要素は、以下のようにフラグメント（<> および </>）で囲む

```App.js
export default function Square() {
  return (
    <>
      <button className="square">X</button>
      <button className="square">X</button>
    </>
  );
}
```

- divでグループ化して、CSSを追加し、マス目を作成

```App.js
export default function Square() {
  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>
    </>
  );
}

```

## propsを通してデータを渡す 

- Reactのコンポーネントアーキテクチャを使って再利用可能なコンポーネントを作成する

- Squareコンポーネントを作成

```App.js
function Square() {
  return <button className="square">1</button>;
}

export default function Board() {
  // ...
}
```

- Boardコンポーネントを更新

```App.js
function Square() {
  return <button className="square">1</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>
    </>
  );
}

```

![スクリーンショット 2024-03-22 6.57.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/ab845653-38f2-da21-91c1-1e4a91bf153e.png)

- すべてのマス目が “1” になっているため、各マス目が持つべき値を親コンポーネント (Board) から子コンポーネント (Square) に伝えるために、propsを使用する

```App.js
function Square({value}) {
  return <button className="square">1</button>;
}
```

- Board コンポーネントが、レンダーしている各Squareコンポーネントにpropsとしてvalueを渡す

```App.js
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>
    </>
  );
}
```

![スクリーンショット 2024-03-22 6.43.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/58d34f07-d76d-c0bd-5ebc-ada5f5df015a.png)

## インタラクティブなコンポーネントの作成
SquareコンポーネントをクリックするとXが表示されるようにする

```App.js
function Square({value}) {
  function handleClick(){
    console.log('clicked!');
  }
  return (
    <button
      className="square"
      onClick={handleClick}
    >
      {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>
    </>
  );
}

```
ここでクリックすると、CodeSandboxのBrowserセクションの下部にあるConsoleタブに"clicked!"というログが表示される

※`Shift + Ctrl + J(Windows/Linux)`または`Option + ⌘ + J (macOS)`というキーボードショートカットで、コンソールを表示できる

![スクリーンショット 2024-03-25 6.22.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/9b64bd81-4b20-90e2-1919-88805870ef26.png)


- 次のステップとしてSquareコンポーネントに、クリックされたことを「記憶」して “X” マークを表示する

Reactには、`useState` という関数があり、コンポーネントからこれを呼び出すことで「記憶」を行わせることができる。Squareの現在の値をstateに保存し、Square がクリックされたときにその値を変更する。

```App.js
// useState をインポート
import { useState } from 'react';

function Square() {
  // useState を呼び出し、valueという名前のstate変数を返す
  // nullは初期値
  const [value,setValue] = useState(null);
  
  function handleClick(){
    console.log('clicked!');
  }
(略)

```

- Squareコンポーネントがpropsを受け取らないようになったので、Boardコンポーネントが作成しているSquareコンポーネントすべてから valueプロパティを削除する

```App.js
（略）
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>
    </>
  );
}
```

Squareをクリックすると “X” が表示されるよう変更する

```App.js
import { useState } from 'react';

function Square() {
  const [value,setValue] = useState(null);

  function handleClick(){
    setValue('X');
  }
  return (
    <button
      className="square"
      onClick={handleClick}
    >
      {value}
    </button>
  );
}
(略)
```

このset関数をonClickハンドラから呼び出すことで、<button> がクリックされるたびにSquareを再レンダーするよう要求。
各Squareはそれぞれ独自のstateを保持。それぞれのSquareに格納されているvalueは、他のものとは完全に独立している。

![スクリーンショット 2024-03-25 6.42.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/1b913daf-94b2-316a-4993-e9f1f3fb25ac.png)

## state のリフトアップ 

stateを各Squareではなく親のBoardコンポーネントに保持させるように変更。Boardコンポーネントは、それぞれのSquareに、何を表示するのかpropsを使って伝えることができる

```App.js
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コンポーネントがonSquareClick関数を呼び出す

```App.js
function Square({ value }) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}
```

onSquareClick関数をSquareコンポーネントのpropsに追加

```App.js
function Square({ value, onSquareClick }) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}
```

このonSquareClickプロパティをBoardコンポーネント内にhandleClickという関数に接続する

```App.js
export default function Board() {
  const [squares, setSquares] = useState(Array(9).fill(null));

  return (
    <>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={handleClick} />
        //...
  );
}
```

マス情報を保持するstateであるsquares配列を更新するため、Boardコンポーネント内にhandleClick関数を定義

```App.js
export default function Board() {
  const [squares, setSquares] = useState(Array(9).fill(null));

  function handleClick() {
    const nextSquares = squares.slice();
    nextSquares[0] = "X";
    setSquares(nextSquares);
  }

```
handleClick関数に、更新するマス目のインデックスを指定する引数iを追加

```App.js
export default function Board() {
  const [squares, setSquares] = useState(Array(9).fill(null));

  function handleClick(i) {
    const nextSquares = squares.slice();
    nextSquares[i] = "X";
    setSquares(nextSquares);
  }

```

これだとうまくいかない。handleClick(0)の呼び出しが、Boardコンポーネントのレンダーの一部として発生するため。
handleClick(0)は、setSquaresを呼び出してBoardコンポーネントの stateを更新するため、Boardコンポーネント全体が再レンダーされる。これによりhandleClick(0)が再度実行され、無限ループになる。

```App.js
<Square value={squares[0]} onSquareClick={handleClick(0)} />

```

この () => handleClick(0)はアロー関数と呼ばれる、関数を短く定義する方法。マス目がクリックされると、アロー (=>)の後のコードが実行され、handleClick(0)がよばれる。これで任意のマス目をクリックしてX が置けるようになる

```App.js
export default function Board() {
(略)
  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>
    </>
  );
};
```

# 手番の処理

先手がデフォルトで “X” になるよう追加。Boardコンポーネントにもう 1つstateを追加し手番を追跡する

```App.js
function Board() {
  const [xIsNext, setXIsNext] = useState(true);
  const [squares, setSquares] = useState(Array(9).fill(null));

(略)
}
```

プレーヤが着手するたびに、どちらのプレーヤの手番なのかを決める xIsNext（真偽値型）が反転して、stateが保存。Boardの handleClick関数を書き換えて、そこでxIsNextの値を反転させる。

```App.js
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);
  }

  (略)
}
```

異なるマス目をクリックするとXとOが正しく交互に表示されるが、同じマスをクリックすると上書きされてしまう

![スクリーンショット 2024-04-05 10.31.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/43681ebb-8cd7-6da3-82be-f206eaff9811.png)

そのマス目に既にXやOの値があるかどうかチェックし、マス目のstateが更新されないようにする

```App.js
function handleClick(i) {
  if (squares[i]) {
    return;
  }
  const nextSquares = squares.slice();

(略)
}
```

# 勝利の宣言

9つのマス目の配列を受け取って勝者を判定する

```App.js
(略)

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

(略)
```

BoardコンポーネントのhandleClick関数でcalculateWinner(squares)を呼び出して、いずれかのプレーヤが勝利したかどうかを判定

```App.js
function handleClick(i) {
  if (squares[i] || calculateWinner(squares)) {
    return;
  }
  const nextSquares = squares.slice();

(略)
}

```

ゲームが終了したことを知らせるために、“Winner: X” または “Winner: O” というテキストを表示する

```App.js

export default function Board() {
  (略)
  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">
  (略)
  )
}


```

![スクリーンショット 2024-04-05 11.05.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/61da2a1d-3c5b-5d1e-5eba-d0924a06c7ef.png)

# まとめ
基本的な処理について学びました！チュートリアルはまだ続きがあるので、実装だけ進めていこうと思います。

# 参考
- [チュートリアル三目並べ](https://arc.net/l/quote/ckzwtzxb)