河野玄斗さんが教える超インド式計算法の演習ができるアプリをNext.jsとAWSで爆速開発してリリース

こんにちは、kenshinです。

先日Youtubeで、医学部に通いながら司法試験に合格、そして頭脳王としても有名な河野玄斗さんが教える、インド式計算法と彼が自分で編み出した超インド式計算法の動画を観ました。

↓動画のリンク
- [インド式計算法](https://www.youtube.com/watch?v=bX8gRF8lUKI)
- [超インド式計算法](https://www.youtube.com/watch?v=kVS-kwWRbCA)

この動画をみることで、2桁の掛け算や3桁の掛け算を、練習を積めば暗算で解くことができるかもしれないとのことです。

しかし、動画内で河野さんが「この授業を聞くだけじゃなく、しっかり聞いた後に演習してください。演習して初めて自分の身になるので。」とおっしゃられていました。

動画を見終わった私は「よし。やり方が分かったから、沢山練習して身に着けるぞ！」と思ったのですが、練習するための問題がないことに気づきました。

本屋でドリルを買ったり、紙に問題を作って解いたりすれば良いのですが、前者はお金がかかる上に問題数に限界があるし、後者は作る手間がかかったりします。

そこで、「ないならば作れ」の考えのもと、今回は「無限に練習問題を作って答えを入力したら、すぐ答え合わせをしてくれるアプリ」を作ろうと思います。

開発からリリースまで１時間もかからないと思うので、気軽にご覧ください。

ちなみに、こちらが完成したサイトです。

https://mental-arithmetic.mk-app.net

![スクリーンショット 2022-03-04 3.47.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2551745/6e33c5e4-0752-01f8-0a94-29c51d7ab2dc.png)

大体の挙動は把握できましたか？

それでははじめましょう。

# 使用技術と開発環境

使用技術はたったのこれだけ。

- Next.js(npmのバージョンは7.18.1)
- TailwindCSS
- Material-ui
- AWS(AWS Amplify)

開発環境はmacOS(M1チップ)

今回の開発で作ったソースコードはGitHubで公開しています。
→`https://github.com/kenshin-morioka`

# Next.jsの準備

まずは、プロジェクトフォルダを作成して、カレントディレクトリをプロジェクトの直下に移動させます。

```:terminal
mkdir mental-arithmetic; cd　mental-arithmetic
```

次にNext.jsのセットアップコマンドを叩きます。(npmのインストールは省略します。)

```:terminal
npx create-next-app . --use-npm
```
Ok to proceed? (y)には、`y`を入力します。

すると以下のようなディレクトリやファイルが生成されると思います。

```
─── mental-arithmetic
    ├── node_modules
    ├── pages
    ├── public
    ├── styles
    ├── .eslintrc.json
    ├── .gitignore
    ├── next.config.js
    ├── package-lock.json
    ├── package.json
    └── README.md
```

ではnpmの実行コマンドで開発サーバーを起動してみます。

```:terminul
npm run dev
```

`localhost:3000`を開くと次のような画面が表示されていたら、準備完了です。

![スクリーンショット.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2551745/4fa9b501-1f5b-c3c3-2387-78fc056d9816.png)


# Materia UIとTailwindCSSの準備

Materia UIをインストールします。

```
npm install @mui/material @emotion/react @emotion/styled
```

続いてTailwindCSSのセットアップを行います。

まずは、TailwindCSSをnpmでインストールします。以下のコマンドを実行してください。

```:terminal
npm install -D tailwindcss postcss autoprefixer
```

次に、initを実行して、`tailwind.config.js`と`postcss.config.js`を作成します。以下のコマンドを実行してください。

```:terminal
npx tailwindcss init -p
```

最後に、`styles/globals.css`と`tailwind.config.js`を次のように変更します。

```css:styles/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

これは、TailwindCSSのディレクティブを追加する設定になります。

```javascript:tailwind.config.js
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```

これはtailwindCSSがpagesフォルダ内のファイルとcomponentsフォルダ内のファイルに反映されるように設定しています。

以上でMateria UIとTailwindCSSの準備が終わりました。また、これらの準備はバージョンによって変わることがありますので、上手くいかないようであれば公式のドキュメントをご覧ください。

# アプリの見た目を構築

それでは本題のアプリの作成に取り掛かります。
まずは、アプリのエントリーポイントである`pages/index.js`を次のように変更します。

```react:pages/index.js
import React, {useState, useEffect} from "react";
import Layout from "../components/Layout";
import Switch from '@mui/material/Switch';

export default function App() {
  const [headItem, setHeadItem] = useState('2桁')
  const [range, setRange] = useState({max: 100, min: 10})
  const [random, setRandom] = useState({left:'', right:''});
  const [checked, setChecked] = useState(false);
  const [ text, setText ] = useState("");
  
  // 正解か不正解かの判定
  let isCorrect = null;
  if (random.left * random.right === Number(text)) {
    isCorrect = true;
  } else {
    isCorrect = false;
  }

  // 解答欄
  const onChangeText = (event) => {
    setText(event.target.value);
  };

  // 「次」ボタンを押した時の処理
  const handleRanddomChange = () =>{
    setRandom({
      left: Math.floor( Math.random() * range.max - range.min ) + range.min,
      right: Math.floor( Math.random() * range.max - range.min ) + range.min
    });
    setText('');
  };

  // 桁数を変更した時の処理
  const handleDigitChange = (event) => {
    setChecked(event.target.checked);
    setText('');
    if (!checked) {
      setRange({max: 1000, min: 100});
      setRandom({
        left: Math.floor( Math.random() * 1000 - 100 ) + 100,
        right: Math.floor( Math.random() * 1000 - 100 ) + 100
      });
      setHeadItem('3桁');
    } else {
      setRange({max: 100, min: 10});
      setRandom({
        left: Math.floor( Math.random() * 100 - 10 ) + 10,
        right: Math.floor( Math.random() * 100 - 10 ) + 10
      });
      setHeadItem('2桁');
    }
  };

  // SSRとクライアントで乱数が違う値になることを防ぐための処理
  useEffect(() => {
    setRandom({
      left: Math.floor( Math.random() * 100 - 10 ) + 10,
      right: Math.floor( Math.random() * 100 - 10 ) + 10
    })
  }, [])
  
  if (!random) {
    return null
  }
  return (
    <Layout title={headItem + "｜暗算練習アプリ"}>
      
      {/* 正解か不正解かの判定 */}
      {text &&
          (isCorrect ?
            <div className="text-2xl text-red-500 mb-8">正解！！</div>
            :
            <div className="text-2xl text-blue-500 mb-8">不正解！！</div>
          )
      }

      {/* 式と解答欄 */}
      <div className="text-2xl sm:text-4xl md:text-6xl text-gray-700 mb-20">
        {random.left} × {random.right} =
        <input
          className="shadow border border-gray-600 rounded ml-6 w-16 sm:w-24 md:w-40"
          type={"text"}
          value={text}
          onChange={onChangeText}
        />
      </div>

      {/* 「次」ボタン */}
      <div className="flex items-center justify-center text-gray-500 text-xl  mb-10
                      bg-yellow-300 hover:bg-yellow-500 rounded-full h-16 w-16"
           onClick={handleRanddomChange}>次</div>

      {/* 桁数の変更 */}
      <div>3桁の掛け算に変更</div>
      <Switch
        checked={checked}
        onChange={handleDigitChange}
        inputProps={{ 'aria-label': 'controlled' }}
      />

    </Layout>
  );
};
```

ここで、`Home.module.css`は必要ないので消してください。

次にプロジェクト直下に`components`フォルダそ作成します。

そしてcomponentsフォルダの中に`Layout.js`というファイルを用意して、次のようにします。

```react:components/Layout.js
import Head from "next/head";

export default function Layout({ children, title = "Nextjs" }) {
    return (
        <div className="flex justify-center items-center flex-col min-h-screen font-mono">

            {/* Headのタイトルを動的に変化 */}
            <Head>
                <title>{title}</title>
            </Head>

            {/* アプリのヘッダー */}
            <header className="flex justify-center items-center bg-gray-600 w-screen 
                               p-6 md:h-20 h-16 md:text-3xl text-lg text-white">
                2,3桁の掛け算の暗算練習アプリ
            </header>

            {/* アプリのメイン */}
            <main className="flex flex-1 justify-center items-center flex-col w-screen">
                {children}
            </main>

            {/* アプリのフッター */}
            <footer className="w-full h-14 flex justify-center items-center
                               border-t bg-gray-600 text-white">
                Powerd by AWS Amplify
            </footer>
        </div>
    );
};
```

開発サーバーで挙動を確認して問題がなければ、これでアプリ自体は完成です！

# AWSでデプロイ

最後に、先ほど作ったアプリをデプロイします。

今回はGitHubを利用することを仮定して話を進めますが、その他のプラットフォーム使用して頂いても問題ありません。

まず、[GitHub](https://github.co.jp/)にて、新規のリポジトリを用意してください。

次に、GitHubの指示に従いながら、以下のコマンドで叩いて、ローカルでの変更をコミット&プッシュしてください。

```:terminul
git add -A
git commit -m 'first commit'
git remote add origin https://github.com/[アカウント名]/[リポジトリ名].git
git branch -M master
git push origin master
```

これで、ローカルの内容がリモートに反映されたかと思います。

次に、[AWS Amplify](https://aws.amazon.com/jp/amplify/)に移動して、「AWS Amplifyを使用してアプリをホストする」を選択してください。

あとは、指示通りに進んでいけばGitHubリポジトリと繋げて、デプロイまで行ってくれると思います。(AWSは頻繁にUIが変わるので詳しい操作は省かせていただきます。)

以上で、暗算トレーニングアプリの構築とデプロイの完成です。