ReactでTic-Tac-Toeのグリッドを.map()関数で動的にレンダリングする方法

Reactにおける.map()関数を用いた空のグリッド描画の課題

ReactでTic-Tac-ToeのようなグリッドUIを構築する際、初期状態で空のマス目を動的にレンダリングしたいというニーズがあります。

特に、.map()関数を空または要素が少ない配列に適用する際の挙動に戸惑うことがあります。この課題に対し、Reactで繰り返し可能なUI要素を生成する.map()関数の効果的な使い方と、CSS Gridを用いたレイアウト構築、そしてゲームの状態管理について解説します。

Solution 1: Array(n).fill()と.map()の組み合わせ

特定の長さを持つ配列を生成する一般的な方法は、Array(n).fill(null) または Array(9).fill(undefined) を使用することです。この配列に対して.map()を適用することで、配列の各要素に対してJSX要素を生成できます。

Tic-Tac-Toeのマス目(divbutton)をレンダリングする例を見てみましょう。

function Board() {
  const initialBoard = Array(9).fill(null);
  return (
    <div className="grid-container">
      {initialBoard.map((_, index) => (
        <button className="square" key={index}>
          {/* マス目の内容は後で追加 */}
        </button>
      ))}
    </div>
  );
}

Reactでリストをレンダリングする際には、各要素に一意なkey propを指定することが不可欠です。これにより、Reactは要素の変更、追加、削除を効率的に識別できます。

CSS Gridによるグリッドレイアウトの実装

Tic-Tac-Toeの3×3グリッドレイアウトは、CSS Gridを使用して実現します。display: gridgrid-template-columns: repeat(3, 1fr)grid-template-rows: repeat(3, 1fr) といったプロパティを設定した.grid-containerクラスにより、子要素であるマス目が自動的にグリッド内に配置されます。

.grid-container {
  display: grid;
  grid-template-columns: repeat(3, 100px);
  grid-template-rows: repeat(3, 100px);
  gap: 5px;
}
.square {
  width: 100px;
  height: 100px;
  border: 1px solid #ccc;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 24px;
  cursor: pointer;
}

Solution 2: ゲーム状態の管理と.map()によるレンダリング

より実践的なアプローチとして、配列を単にマス目の存在を示すだけでなく、その状態(’X’, ‘O’, または空を表すnull/undefined)を格納するために使用します。ReactのuseStateフックを用いて初期状態を配列で設定し、.map()関数で各マス目の状態に応じたコンテンツ(またはスタイル)をレンダリングします。

import React, { useState } from 'react';
function Board() {
  const [squares, setSquares] = useState(Array(9).fill(null));
  const handleClick = (index) => {
    const newSquares = [...squares];
    // 既にマス目が埋まっている場合は何もしない
    if (newSquares[index] || calculateWinner(newSquares)) {
      return;
    }
    // ここで現在のプレイヤー ('X' or 'O') を決定するロジックが必要
    newSquares[index] = 'X'; // 仮に'X'で埋める
    setSquares(newSquares);
  };
  const renderSquare = (index) => {
    return (
      <button className="square" onClick={() => handleClick(index)} key={index}>
        {squares[index]}
      </button>
    );
  };
  return (
    <div className="grid-container">
      {squares.map((_, index) => renderSquare(index))}
    </div>
  );
}
// 勝者判定などの追加ロジックは省略
function calculateWinner(squares) {
  // ... 実装 ...
  return null;
}
export default Board;

このアプローチでは、useStateで管理されるsquares配列がゲームの盤面状態を表します。`.map()`は各マス目をレンダリングし、クリックイベント(onClick)によって状態が更新されると、コンポーネントが再レンダリングされ、画面に反映されます。

プレイヤーのターンとクリック処理

handleClick関数は、クリックされたマス目のインデックスを受け取り、squares配列のコピーを作成して該当するインデックスの値を更新します。状態の更新 (setSquares) がReactに再レンダリングをトリガーさせます。既に埋まっているマス目やゲームが終了している場合のクリックを無効にするロジックを追加することで、より堅牢なゲームプレイを実現できます。

.map()コールバックの精緻化:動的なコンテンツ

.map()関数内でレンダリングされる要素をさらに動的にすることができます。例えば、マス目の内容 (squares[index]) をボタン内に表示したり、セルの状態に基づいて異なるCSSクラスを適用したりすることが可能です。.map()の第二引数であるindexは、各セルを一意に識別するために非常に重要であり、key propの指定や、将来的なロジックの実装に役立ちます。

まとめ

ReactでTic-Tac-ToeのようなグリッドUIを動的にレンダリングするには、Array(n).fill().map()による初期描画と、useStateを用いたゲーム状態管理によるインタラクティブな更新という2つの主要なアプローチがあります。

CSS Gridは効率的なレイアウト構築に不可欠であり、Reactのリストレンダリングにおけるkey propの重要性も再確認しました。これらの概念を自身のTic-Tac-Toeプロジェクトや他のUI開発に応用してください。

タイトルとURLをコピーしました