React入門５: リファクタリング [インタラクション編]

# はじめに
私自身の復習兼、備忘録的な意味もあり、複数回の記事に渡って React を用いてマルバツゲーム(三目並べ)を開発していきたいと思います。
- [React](https://ja.react.dev/)
- [CodeSandbox(公式)](https://codesandbox.io/)
- [CodeSandbox(非公式)](https://csb-jp.github.io/)

## シリーズの一覧
1. [React入門１: 環境構築 [オンライン版]](https://qiita.com/banana_bancho/items/64fa6ccb58101eadbb63)
   - [React入門1.5: 環境構築 [ローカル版-番外編]](https://qiita.com/banana_bancho/private/30f5d9d2c5eea00ac11c)
1. [React入門２: 盤面の作成](https://qiita.com/banana_bancho/private/c50495b5d4c30555cec3)
1. [React入門３: インタラクションの実装](https://qiita.com/banana_bancho/private/557d14b2413cb0317ce4)
1. [React入門４: リファクタリング [リフトアップ編]](https://qiita.com/banana_bancho/private/81730326c004e52c6aad)
1. React入門５: リファクタリング [インタラクション編] (今回)
1. [React入門６: 手番の実装](https://qiita.com/banana_bancho/private/6bed838f67e575f58b72)
1. [React入門７: ゲームの勝利判定](https://qiita.com/banana_bancho/private/c0eca4e9348edb430561)
1. [React入門８: テキストの実装](https://qiita.com/banana_bancho/private/b841b1ce38482b31753c)
1. [React入門９: タイムトラベル(1)](https://qiita.com/banana_bancho/private/c674872e32fcb52bb2a0)
1. [React入門10: タイムトラベル(2)](https://qiita.com/banana_bancho/private/861398f1fa4efd378938)
1. [React入門11: タイムトラベル(3)](https://qiita.com/banana_bancho/private/0d3cf7e1b1ebbf20c5e0)

# 目的について
## 全体の目的
React公式のチュートリアルで公開されているマルバツゲームを 3x3 のマスで実装していきます。

https://ja.react.dev/learn/tutorial-tic-tac-toe

## 今回の目的
前回のリフトアップで失われたインタラクションを実装して、リファクタリングを完了していきます。

# リファクタリング(Part2)
次のページで、前回のソースファイルを確認できます。

https://codesandbox.io/p/sandbox/react-tutorial4-vj36hl

- 前回の内容はコチラから！

https://qiita.com/banana_bancho/items/81730326c004e52c6aad


## インタラクションの再実装
早速、コーディングをしていきます。App.js に次のように変更しましょう。
- ```Square``` コンポーネントの変更
    - プロパティとして ```onSquareClick``` を追加する
    - ```<button>``` 要素に ```onClick``` プロパティを追加して、本コンポーネントの ```onSquareClick``` プロパティを渡す
- ```Board``` コンポーネントの変更
    - ```handleClick()``` 関数を作成する
        - 仮引数はなし
        - コンソールにクリックされた旨を出力させる
    - 1つ目の ```Square``` コンポーネントに ```onSquareClick``` プロパティを追加して、 ```handleClick()``` 関数を渡す

```jsx:App.js
import { useState } from "react";
import "./styles.css";

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() {
    console.log("clicked");
  }

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

コンソールを開いて( [表示方法: [画面の内容 > プレビュー > (A)] 参照](https://qiita.com/banana_bancho/items/64fa6ccb58101eadbb63#%E7%94%BB%E9%9D%A2%E3%81%AE%E5%86%85%E5%AE%B9) )、左上のマスに対してマウスクリックをすると、コンソール上に「clicked!」と表示されます。なお、左上以外のマスに対してクリックをしても、反応はありません。動作結果を次に示します(音がないのでクリックしていることが伝わりずらいと思いますが...)。

![output_video.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2844961/e6e0b4cb-324a-b8cb-2e96-e58575b4b7a2.gif)


## 左上にあるマスの表示変更
次に、マスがクリックされると「X」を表示するようにしていきます。App.js の ```Board``` コンポーネントで定義した ```handleClick()``` 関数を次のように変更しましょう。
- ```nextSquares``` 配列を宣言する
    - 初期値として ```state``` 型の ```squares``` 配列のコピーを代入する
- ```nextSquares``` 配列の第0要素に ```"X"``` を代入させる
- ```state``` 型の ```squares``` 配列を ```nextSquares``` 配列に更新する
- コンソールへの出力処理は削除する
```jsx:handleClick() 関数の変更
function handleClick() {
  const nextSquares = squares.slice();
  nextSquares[0] = "X";
  setSquares(nextSquares);
}
```

これで左上のマスをクリックすると、「X」が表示されるようになりました。先ほどと同様にここでも、左上以外のマスに対してクリックをしても、反応はありません。

![output_video.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2844961/96d25d17-cd82-49e7-e7fd-462462ee53f4.gif)

## 全てのマスの表示変更
左上以外のマスでもクリックされたときに、「X」を表示できるように、App.js を次のように変更しましょう。
- ```Board``` コンポーネントの変更
    - ```handleClick()``` 関数の変更
        - 仮引数 ```i``` を追加する
        - ```nextSquares``` 配列の第0要素に関する処理を削除する
        - ```nextSquares``` 配列の第 ```i``` 要素に ```"X"``` を代入させる
    - ```Square``` コンポーネントの ```onSquareClick``` プロパティを変更する
        - ```x``` 番目の ```Square``` コンポーネントにある ```onSquareClick``` プロパティには、<INS>```handleClick(x)``` の処理を実行する関数</INS>を渡す( [解説](#再レンダーによる無限ループ) )
```jsx:App.js
import { useState } from "react";
import "./styles.css";

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();
    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>
    </>
  );
}
```

実行結果を次に示します。どのマスもクリックされると、「X」が表示されるようになりました。

![output_video.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2844961/0c7ff1b2-3278-e9da-82ad-950b63375694.gif)









### 再レンダーによる無限ループ
先ほどの [リファクタリングの変更内容](#全てのマスの表示変更)  で含みのある言い方をしましたね。

>- ```x``` 番目の ```Square``` コンポーネントにある ```onSquareClick``` プロパティには、<INS>```handleClick(x)``` の処理を実行する関数</INS>を渡す

この内容は ```Board``` コンポーネントの ```return``` 文にある ```Square``` コンポーネントのプロパティに記述された「 ```onSquareClick={() => handleClick(0)}``` 」のような箇所が該当します。

これを「 ```onSquareClick={handleClick(0)}``` 」と書き換えてみましょう。
```jsx:App.js
import { useState } from "react";
import "./styles.css";

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();
    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>
    </>
  );
}
```

この場合、```onSquareClick``` プロパティに関数を渡すはずが、```handleClick(0)``` という処理を実行することになります( [自動セミコロン挿入ASI](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Lexical_grammar#自動セミコロン挿入) によってセミコロン ```;``` の省略は許容されます)。つまり、クリックのイベントと関数の発火が紐づきません。

イベントと関数が連携されていないのであれば、マスのクリックから処理が発生せず何も起きないただのバグになるはずです。つまり、アプリケーションの動作が停止するような致命的な問題とはなりえません。

それでは、次に実行結果を示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2844961/57102fea-fcfd-0cd3-2373-de884bc01b69.png)

次のエラーが出力されてしまいます。
```
Error
Too many re-renders. React limits the number of renders to prevent an infinite loop.
```

これは、爆発的な回数の再レンダーから無限ループが発生している恐れがあり、React がアプリケーションの実行を停止させていることを示しています。

再レンダーの原因は ```handleClick()``` 関数の処理にある ```setSquares()``` 関数です。これは、[前回の記事](https://qiita.com/banana_bancho/private/81730326c004e52c6aad#マスの状態を管理するコンポーネント) で実装した ```state``` 型の ```squares``` 配列を更新する set 関数です。[第3回の記事](https://qiita.com/banana_bancho/private/557d14b2413cb0317ce4#state-コンポーネントと-usestate-関数) に記したように、set 関数はコンポーネントを再レンダーする仕様となっています。

つまり、「 ```onSquareClick={handleClick(0)}``` 」と記述すると、0番目の ```Square``` コンポーネントを呼び出すときに ```handleClick(0)``` が実行され、```setSquares()``` 関数の処理によって ```Board``` コンポーネントが再レンダーされます。そして、再レンダーされた ```Board``` コンポーネントが、0番目の ```Square``` コンポーネントを呼び出すときに ```handleClick(0)``` を実行して...、と処理が永遠に続きます。

![再レンダーの無限ループ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2844961/a620a2b9-5b63-246e-0ab0-a5c6b4109567.jpeg)




そこで、```Square``` コンポーネントのプロパティに無名関数を定義して渡すことで、再レンダーによる無限ループを解決していきます。正しい変更後のコード「 ```onSquareClick={() => handleClick(0)}``` 」は、アロー関数を用いて無名関数を定義しています。次のWebページの「解説」という項目を確認するとその成り立ちを確認できます。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions#解説

> ```js: 『アロー関数式 - JavaScript | MDN』より引用
> // 従来の無名関数
> (function (a) {
>  return a + 100;
> });
> 
> // 1. "function" という語を削除し、引数と本体の開始中括弧の間に矢印を配置する
> (a) => {
>   return a + 100;
> };
> 
> // 2. 本体の中括弧を削除と "return" という語を削除 — return は既に含まれています。
> (a) => a + 100;
> 
> // 3. 引数の括弧を削除
> a => a + 100;
> ```


定義した無名関数 ```()``` を ```onSquareClick``` プロパティに渡すことで、即時的に関数を実行せずに、イベントの発火(マウスクリック)に応じて処理されます。

# おわりに
今回は、インタラクションを再実装することで、リフトアップによるリファクタリングを完了させました。次のページに現段階のソースファイルを示します。

https://codesandbox.io/p/sandbox/react-tutorial5-xsx53w

次回は、マスが「X」だけでなく「O」も表示できるように実装していきます。

https://qiita.com/banana_bancho/private/6bed838f67e575f58b72