しりとりアプリ開発：Reactで作るシンプルなしりとりアプリをDockerで動かすまでの手順

## はじめに
本記事では、Docker環境でReactを使用してシンプルなしりとりアプリを作成する手順について説明します。

今回は、ユーザーが前の単語の最後の文字から始まる単語を入力し、正しくしりとりを続けることができるアプリを実装しています。

本記事はITスクールの課題としてアウトプット学習を目的に作成したもので、個人の備忘録レベルの内容ですのでご了承ください。

## 必要なツール

:::note
**私の環境はM1チップ搭載のMacOSですが、本記事のハンズオンで使用するツールは以下の通りです。**
・``Docker``
・``Docker Compose``
・``Node.js（バージョン14以上推奨）``
・``npm（Node Package Manager）``
:::

## 作成したしりとりアプリのデモ動画
以下はしりとりアプリのデモ動画です。ユーザーが提示された単語に続く単語を入力すると、正確に入力したタイミングで結果が表示されます。

![タイトルなし.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/a83ecfa0-8cbc-1688-9cdf-767d790fdc7f.gif)

:::note warn
**気軽に楽しめるアプリとして、しりとりの練習のきっかけになれば幸いです。**
:::

## ディレクトリ構成
以下のようなディレクトリ構成でReactアプリを構築します。この構成により、しりとりアプリのファイルを適切に配置し、Docker環境で動作させることができます。

```bash
shiritori-app/
│
├── node_modules/         # Node.jsのモジュールが格納されるディレクトリ
│
├── public/               # 公開用のファイル
│   └── index.html        # アプリケーションのエントリーポイントとなるHTMLファイル
│
├── src/                  # ソースコードのディレクトリ
│   ├── Shiritori.js      # しりとりのロジック
│   ├── Shiritori.css     # しりとりのスタイルシート
│   ├── index.js          # アプリケーションのエントリーポイント
│   └── index.css         # アプリケーションのスタイルシート
│
├── .dockerignore         # Dockerビルド時に無視するファイル
├── Dockerfile            # Dockerイメージを作成するための設定ファイル
├── docker-compose.yml    # Docker Composeの設定ファイル
├── package.json          # プロジェクトの依存関係やスクリプトを管理するファイル
└── README.md             # プロジェクトの説明や使い方を記載したファイル（任意）
```

このディレクトリ構成を作成するために、以下のコマンドを実行して初期ファイルを生成し、不要なファイルを削除して整理します。

```
npx create-react-app shiritori-app
```

:::note warn
**上記のコマンドを実行するとデフォルトで多数のファイルが作成されますが、ここでは不要なファイルを削除して進めます。**
:::

## ステップ1: プロジェクトの作成
### Dockerfileの作成
まず、プロジェクトのルートディレクトリに以下の内容で ``Dockerfile`` を作成します。

```Dockerfile
# Dockerfile
FROM node:14

# 作業ディレクトリの設定
WORKDIR /app

# 依存関係のインストール
COPY package*.json ./
RUN npm install

# アプリケーションのソースをコピー
COPY . .

# アプリケーションをビルド
RUN npm run build

# アプリケーションを提供する
CMD ["npm", "start"]
```

この ``Dockerfile`` は、Dockerイメージを作成するための設定ファイルです。Node.js 環境を構築し、アプリケーションの依存関係をインストールし、最終的にアプリをビルドします。

### docker-compose.ymlの作成
次に、プロジェクトのルートディレクトリに以下の内容で ``docker-compose.yml`` を作成します。

```docker-compose.yml
version: '3'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
    environment:
      - CHOKIDAR_USEPOLLING=true
```

このファイルは、Docker Composeを使用してアプリケーションのサービス構成を定義するものです。アプリケーションのビルド、ポートの公開、ボリュームの設定などを管理します。

## ステップ2: Reactアプリの作成
ここでは、しりとりアプリの機能を実装するためのファイルをプロジェクトに追加します。

### Shiritori.js
以下は Shiritori.js のコードです。このコードでは、前の単語の最後の文字から始まる単語を正確に入力するためのしりとりロジックを実装しています。

```Shiritori.js
// src/Shiritori.js
import React, { useState, useEffect } from 'react';
import './Shiritori.css';

const Shiritori = () => {
  const words = [
    "りんご",    // りんご → ご
    "ごま",      // ごま → ま
    "まんが",    // まんが → が
    "がんばる",  // がんばる → る
    "るすばん",  // るすばん → ん
  ];

  const [input, setInput] = useState("");
  const [currentWord, setCurrentWord] = useState("");
  const [result, setResult] = useState([]);

  // 最後の文字から始まる単語を選択する関数
  const pickNextWord = () => {
    const lastChar = currentWord.slice(-1);
    const nextWords = words.filter(word => word.startsWith(lastChar));

    if (nextWords.length > 0) {
      const nextWord = nextWords[Math.floor(Math.random() * nextWords.length)];
      setCurrentWord(nextWord);
    } else {
      setResult(prev => [...prev, "もう続けられません！"]);
    }
  };

  useEffect(() => {
    const firstWord = words[Math.floor(Math.random() * words.length)];
    setCurrentWord(firstWord); // 最初の単語を選択
  }, []);

  const handleChange = (e) => {
    const userInput = e.target.value;
    setInput(userInput);

    // 入力が空でない場合にのみチェックを行う
    if (userInput) {
      const lastChar = currentWord.slice(-1);

      // 正しい単語の入力チェック
      if (userInput === currentWord) {
        setResult(prev => [...prev, "成功！"]);
        setInput("");
        pickNextWord(); // 成功したら新しい単語を選択
      } else if (!userInput.startsWith(lastChar)) {
        setResult(prev => [...prev, `その単語は「${lastChar}」から始まりません！`]);
      } else {
        setResult(prev => [...prev, "成功！"]);
        setInput("");
        setCurrentWord(userInput); // 入力した単語を次の単語に設定

        // 次の単語が選ばれるかどうかの確認
        const nextWords = words.filter(word => word.startsWith(userInput.slice(-1)));
        if (nextWords.length === 0) {
          setResult(prev => [...prev, "もう続けられません！"]);
        }
      }
    }
  };

  return (
    <div className="container">
      <h1>しりとりゲーム</h1>
      <p>今現在の単語: <strong>{currentWord}</strong></p>
      <p>次の単語の最初の文字: <strong>{currentWord.slice(-1)}</strong></p>
      <input 
        type="text" 
        value={input} 
        placeholder={`次の単語は「${currentWord.slice(-1)}」から始まります`}
        onChange={handleChange} 
      />
      <p>{result[result.length - 1]}</p>
      <h2>使える単語一覧</h2>
      <ul>
        {words.map((word, index) => (
          <li key={index}>{word}</li>
        ))}
      </ul>
    </div>
  );
};

export default Shiritori;
```

### Shiritori.css
以下は ``Shiritori.css`` のコードです。しりとりフィールドと結果表示にスタイルを適用しています。

```Shiritori.css
/* src/Shiritori.css */
body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background-color: #e6e6fa; /* 薄い紫 */
  margin: 0;
}

.container {
  text-align: center;
  font-family: 'Arial', sans-serif;
}

input {
  font-size: 24px;
  padding: 10px;
  margin-top: 20px;
}

p {
  font-size: 20px;
}
```


### index.js
以下は ``index.js`` のコードです。Reactのエントリーポイントとして、アプリケーションをレンダリングしています。

```index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import Shiritori from './Shiritori';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Shiritori />
  </React.StrictMode>
);
```

### index.css
次に、全体のスタイルを設定するための ``index.css`` ファイルをプロジェクトに追加します。

```index.css
/* src/index.css */
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  background-color: #f0f0f0;
}
```

## ステップ3: アプリの起動
最後に、以下のコマンドでアプリケーションを起動します。

```bash
# Docker Composeを使ってアプリケーションを起動
docker-compose up
```

ブラウザで、``http://localhost:3000/``を入力すると、以下のページが表示されていれば成功です。

![スクリーンショット 2024-11-03 18.48.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/fd3b9b1e-94ce-0229-f80c-5efe179495ed.png)

ここでは「**使える単語一覧**」の中からしかしりとりができないという、かなりの欠陥アプリになっています。笑

![スクリーンショット 2024-11-03 18.51.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/f74c63db-3cbd-627f-5d47-5b49af5a4619.png)

:::note warn
**ちょうど「るすばん」と入力してしまったため、しりとりはそこで終わってしまいました。笑**
:::


## まとめ

今回はDockerを使ってしりとりアプリを実装しましたが、機能としてはかなりの欠落があります。

時間的な制約もあり、これ以上の調査と実装は断念しましたが、余裕があれば修正を予定しています。

今後は、一旦フロント部分の開発はここまでとし、バックエンド側の開発に本格的に着手していきます。

## 参考文献

https://shamaton.dev/posts/2022031401/