Overview(概要)

tic-tac-toe-init

CodeSandbox には次の3つの主要なセクションがあります。

CodeSandboxの説明1

  1. Filesには下記があります。
    • App.js
    • index.js
    • styles.css
    • package.json
    • public/というフォルダ
  2. 選択したファイルのソースコードが表示されるコードエディター
  3. 作成したコードがどのように表示されるかを確認するブラウザーセクション

FilesのApp.jsファイルを選択してください。 コードエディターでそのファイルの内容を確認してください。

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

ブラウザーセクションには下記のような四角にX印が表示されていると思います。

では中身のファイルを見てみましょう。

App.js

App.jsのコードはコンポーネントを作成します。
Reactでは、コンポーネントはUI(ユーザーインターフェース)の一部を表す再利用可能なコードの一部です。
コンポーネントは、アプリケーションのUI要素をレンダリング、管理、更新するために使用されます。
コンポーネントを一行ずつ見て、何が起こっているのかを確認しましょう。

export default function Square() {
    // ↑ この行は、Squareという名前の関数を定義しています。
    return <button className="square">X</button>;
}

最初の行はSquareという関数を定義している。
JavaScriptのexportキーワードは、この関数をファイル外からアクセスできるようにします。
defaultキーワードは、あなたのコードを使用している他のファイルに、この関数があなたのファイルのメイン関数であることを知らせます。

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

2 行目のコードはボタンを返しています。
return という JavaScript キーワードは、後に書くものが関数の呼び出し元に値として返されるということを意味します。
この <button>JSX 要素 (JSX element) と呼ばれます。
JSX 要素とは、何を表示したいかを記述するための JavaScript コードと HTML タグの組み合わせです。
className="square" はこのボタンのプロパティ、または props と呼ばれるもので、CSS にボタンをどのようにスタイル付けするか伝えます。
X はボタンの内部に表示されるテキストです。
</button> は JSX 要素を閉じて、これ以降に書かれた内容がボタンの内部に出てこないようにします。

styles.css

CodeSandbox の Files セクションにある styles.css というファイルをクリックしてください。
このファイルには、React アプリのスタイルが定義されています。
最初の 2 つの CSS セレクタ*body)は、アプリケーションの全体的なスタイルを定義しており、.square というセレクタは、className プロパティが square となっているコンポーネントのスタイルを定義します。
今回のコードでは、これは App.js ファイルの Square コンポーネントが表示するボタンにマッチします。

* {
    box-sizing: border-box;
}

body {
    font-family: sans-serif;
    margin: 20px;
    padding: 0;
}

/* 省略 */

.square {
    background: #fff;
    border: 1px solid #999;
    float: left;
    font-size: 24px;
    font-weight: bold;
    line-height: 34px;
    height: 34px;
    margin-right: -1px;
    margin-top: -1px;
    padding: 0;
    text-align: center;
    width: 34px;
}

/* 省略 */

index.js

CodeSandbox の Files にある index.js というファイルをクリックしてください。 このチュートリアルでこのファイルを編集することはありませんが、App.js ファイルで作成したコンポーネントと Web ブラウザとの橋渡しを行っています。

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

1〜4 行目で、必要なすべての部品を取り出しています:

  • React
  • Web ブラウザとやり取りするための React ライブラリ (React DOM)
  • コンポーネント用のスタイル
  • App.js であなたが作成したコンポーネント

ファイルの残りの部分では、これらの部品を全部まとめて、最終的な成果物を public フォルダ内の index.html に注入しています。

盤面の作成

それでは App.js に戻りましょう。このチュートリアルの残りは、このファイル内で作業します。

現在の盤面 (board) にはマス目 (square) が 1 つしかありませんが、本来は 9 つ必要です!
2 つ目のマス目を作るために単純にコピーペーストすると…

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

以下のようなエラーが表示されます: error

React コンポーネントからは、このボタンのように JSX 要素を複数隣り合わせて返すのではなく、単一の JSX 要素を返す必要があります。これを修正するには、複数の隣接する JSX 要素は、以下のようにフラグメント<> および </>)で囲むようにします。

// App.js を以下のように変更してください
export default function Square() {
    return (
        <>
            <button className="square">X</button>
            <button className="square">X</button>
        </>
    );
}

これで以下のように表示されるはずです:

素晴らしいです! あとは、マス目が 9 個になるまで何度かコピーペーストすれば…

あれ? 盤面のマス目はグリッド状に並べたいのですが、1 行に並んでしまっています。これを修正するには、div を使って複数のマス目を行単位でグループ化し、CSS クラスを追加する必要があります。ついでに各マス目に番号をつけて、どれがどこに表示されているのか確認できるようにしましょう。

App.js ファイルで、Square コンポーネントを以下のように書き換えてください:

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

styles.css で定義されている CSS が、classNameboard-row となっている div をスタイル化します。スタイル化された div でコンポーネントを 3 行にまとめたので、三目並べの盤面ができました。

しかし別の問題が出てきました。Square という名前のコンポーネントなのに、実際にはもう 1 個のマス目ではなくなっています。これを直すため、名前を Board に変えます。

export default function Board() {
    // SquareをBoardに変更 ...
}

この段階で、コードは次のようになっているはずです。

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

Note
うーん、ちょっとタイピングが大変ですよね! このページからコードをコピーペーストしても問題ありません。ただし、ちょっと挑戦してみたい気分であれば、自分で 1 度は手入力したものだけをコピーすることをおすすめします。

ここまでの状態ををCodeSandboxに保存しています。わからなくなったかたは下記からコピーできます。 https://codesandbox.io/s/tic-tac-toe-part2-1-4cp9j2

propsを通してデータを渡す

次に、ユーザがマス目をクリックしたら、空白だった中身が "X" に変化するようにしたいと思います。ですが先ほどのように盤面を作成していたのでは、この先マス目の中身を更新するコードを 9 回(各マス目に対して 1 回ずつ)コピーペーストしなくてはならなくなってしまいます! そのようなコピーペーストをする代わりに、React のコンポーネントアーキテクチャを使って再利用可能なコンポーネントを作成することで、重複だらけのごちゃごちゃとしたコードを書かずに済むようになります。

まず、Board コンポーネントから最初のマス目を定義している行 (<button className="square">1</button>) をコピーし、新たに書く Square コンポーネントに貼り付けます。

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

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

次に、Board コンポーネントを更新し、JSX 構文を使用してこの Square コンポーネントをレンダーするようにしましょう。

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

ブラウザの div とは異なり、自分で作成するコンポーネントである BoardSquare は、大文字で始める必要があることに注意してください。

どうなったか見てみましょう。

あれ? 先ほどまでの番号付きのマス目がなくなってしまいました。すべてのマス目が "1" になってしまっています。これを修正するには、各マス目が持つべき値を親コンポーネント (Board) から子コンポーネント (Square) に伝えるために、props というものを使用します。

Square コンポーネントを更新して、Board から渡される value プロパティを読み取るようにします。

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

function Square({ value }) は、Square コンポーネントに props として value という名前の値が渡されることを示しています。

次は各マス目に、受け取った value を表示させる必要があります。次のようにしてみましょう。

function Square({ value }) {
    return <button className="square">value</button>;
}

おっと、これは意図したものではありません。

value

コンポーネントから value という名前の JavaScript 変数の値を表示させたかったのであって、"value" という単語自体を表示させたかったわけではありませんね。ここでは JSX の中から「JavaScript の記法に戻る」ために、波括弧が必要です。JSX の中で value の周りに波括弧を追加してみましょう。

function Square({ value }) {
    return <button className="square">{value}</button>;
}

今のところ、空白の盤面が表示されているはずです。

これは Board コンポーネントが、レンダーしている各 Square コンポーネントにまだ props として value を渡していないからです。これを修正するには、Board コンポーネントがレンダーしている Square コンポーネントのそれぞれに、props として value を追加していきます:

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

これで、再び数値が入ったグリッドが表示されるようになりました:

ここまでで、コードは以下のようになっているはずです:

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

ここまでの状態をCodeSandbox に保存しています。わからなくなった方は下記からコピーできます。 https://codesandbox.io/s/tic-tac-toe-part2-2-62sxpj

インタラクティブなコンポーネントの作成

では Square コンポーネントをクリックすると X が表示されるようにしてみましょう。Square の中に handleClick という関数を宣言します。次に、Square から返される button JSX 要素に、props として onClick を追加します。

function Square({ value }) {
    function handleClick() {
        console.log("clicked!");
    }

    return (
        <button className="square" onClick={handleClick}>
            {value}
        </button>
    );
}

ここでクリックしてみると、CodeSandbox の Browser セクションの下部にある Console タブに "clicked!" というログが表示されるはずです。複数回クリックすると、再び "clicked!" がログとして記録されますが、同一のメッセージが繰り返しコンソールに表示されることはありません。代わりに、最初の "clicked!" ログの隣にカウンタが表示され、その数字が増えていきます。

次のステップとして、Square コンポーネントに、クリックされたことを「記憶」して "X" マークを表示してもらうことにします。何かを「記憶」するために、コンポーネントは state というものを使用します。

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

ファイルの先頭で useState をインポートします。Square コンポーネントから value プロパティを削除します。代わりに、Square の先頭に新しい行を追加して useState を呼び出します。value という名前の state 変数が返されるようにします。

import { useState } from 'react';

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

  function handleClick() {
    //...

value が state の現在値を格納し、setValue はその値を更新するために使う関数です。useState に渡される null は、この state 変数の初期値として使用されるので、この value はまず null という値から始まります。

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

// ...
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" が表示されるようにします。イベントハンドラの console.log("clicked!");setValue('X'); に置き換えます。これで、Square コンポーネントは次のようになります。

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

    function handleClick() {
        setValue("X");
    }

    return (
        <button className="square" onClick={handleClick}>
            {value}
        </button>
    );
}

この set 関数を onClick ハンドラから呼び出すことで、<button> がクリックされるたびに React に Square を再レンダーするよう要求しています。更新の後では当該 Squarevalue'X' になっているので、ゲームの盤面上に "X" が表示されるようになります。いずれかのマス目かをクリックすると "X" が表示されるはずです。

Xを追加

各 Square はそれぞれ独自の state を保持しています。それぞれの Square に格納されている value は、他のものとは完全に独立しています。コンポーネントの set 関数を呼び出すと、React は自動的に内部にある子コンポーネントも更新します。

上記の変更を行った後、コードは次のようになります。

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

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

React Developer Tools

React DevTools を使うと、React コンポーネントの props や state を確認することができます。React DevTools タブは、CodeSandbox の Browser セクションの下部にあります。

devtool

画面上の特定のコンポーネントについて調べるには、React DevTools の左上にあるボタンを使用します。

devtooldemo

ここまでの状態をCodeSandbox に保存しています。わからなくなった方は下記からコピーできます。 https://codesandbox.io/s/tic-tac-toe-part2-3-9345tp

Previous
Next

results matching ""

    No results matching ""