0
0

【アプリ制作実践】ReactとPythonでクイズアプリを作ろう!!

Last updated at Posted at 2024-09-15

スクリーンショット 2024-09-16 3.02.05.png

初めに

 今回、「コードの学習をしたけど実際にアプリを作ってみたい!」という方に向けてアプリ制作を実際に行えるようなHowToブログを書いてみよう!と思って記事を書きました!


 自分自身実際にコードを書いてみたいなと思いつつも「何をどんなふうに作ればいいの?」と思っていたことがあったのでそのような悩みを解決できればと思います!


*おねがい*
今回、より簡単に制作してほしいということを目的としているため、実務的ではない部分があります。ご了承ください また、今回制作するものに関してはローカルでの実行ができることを目標としております。

今回使うもの

  • VScord
  • React
  • FastAPI (Pythonのフレームワークです)

今回制作するもの

  • 初めに「問題文」、「選択肢(ボタン付き)」、「答え合わせボタン」が表示されている
  • 問題が表示されて「答え合わせ」ボタンを押すと「問題の正誤」、「次の問題へ」ボタンが表示される
  • 「次の問題へボタン」を押すと次の問題が表示される

前提条件

  • プログラミングに関する基礎知識
  • Python と Node.js がインストールされていること。
  • FastAPI と React を使用するために、以下のツールもインストールされていることを確認してください。
    -pipenv (Python 仮想環境管理ツール)
    -npm (Node パッケージマネージャー)

1.まず初めに

 ます初めに今回制作するアプリ用のフォルダを作ります
フォルダアプリなどで、「exsercise-quiz-app」などのわかりやすい名前をつけておきましょう!
スクリーンショット 2024-09-16 2.49.22.png

作成できましたらVScordの左上の「file」→「Open Folder」から作ったフォルダを開いておきましょう!
ここからはVSCord上で操作します。
スクリーンショット 2024-09-16 2.49.54.png

2.FastAPIの設定

2-1.Pipenv による仮想環境の作成と依存関係のインストール

ここで今回仮想環境で使うpipenvをインストールしていきます。

仮想環境について書くと長くなるので、今回は難しいことは考えずに、アプリを制作する上で使用する環境を整える手順だと思ってください笑(詳しく知りたい人はGPTに聞いてみたりしてください!)

先ほどの手順でVScordを開いていると思うので、左上の4つのアイコンのうち右から2番目のアイコンを押してください。
スクリーンショット 2024-09-16 2.51.03.png

そうすると画面下側にターミナル画面が出てくると思うのでここでコマンドを入力します。

pipenv install fastapi uvicorn

上記のコマンドを入力し、仮想環境を開始します。
pip.fileとpiplock.fileというファイルが追加されれば成功です!

2-2.仮想環境の有効化

仮想環境を有効にする、ために以下のコマンドを入力します

pipenv shell

これで準備が整いました!
次の手順からやっとコードを書いていきます

3.FastAPI の API を作成する

ここからコードを書いていきます!
ディレクトリ内に「 main.py 」というファイルを作って以下の手順に従ってコードを書いてみてください!

3-1 ライブラリのインポート

from fastapi import FastAPI
from pydantic import BaseModel

この部分に関しては今回こアプリで使っていくライブラリという便利なアイテムをインポートしていきます。
それぞれのライブラリに関しては以下に軽く概要を書いておきます!

  • FastAPI: 今回FastAPIライブラリを使ってWebアプリケーションのAPI(バックエンドの機能)を作成します。
  • BaseModel: Pydanticライブラリからのクラスで、データのバリデーションやモデルの定義に使います。

3-2.FastAPIアプリケーションの作成

app = FastAPI()

ここではFastAPIのアプリケーションインスタンスを作成します。このインスタンスを使って、APIのルート(エンドポイント)を定義します!

3-3.問題モデルの定義

class Question(BaseModel):
    question: str
    options: list
    answer: str

ここでは今回使用する問題をモデルとして定義します!

  • Questionクラス: 今回問題を格納するクラスで、クイズの問題を表現するためのデータモデルです。
  • BaseModel: PydanticのBaseModelを継承しており、データの型や構造を定義します。
  • question: 問題のテキスト(文字列)を格納します。
  • options: 選択肢のリストを格納します。選択肢は文字列のリストです。
  • answer: 正しい答えを示す文字列です。

3-4.クイズの問題データを配列に格納

ここでは実際に問題を設定していきます!
今回はとりあえず以下のような問題を設定しておりますが、皆様の好きなように設定してください!
上記の手順で定義したようにquestion,options,answerにそれぞれ問題文、選択肢、答えを設定していきます。

questions = [
    {
        "question": "地球はどの惑星系に属していますか?",
        "options": ["A. 太陽系", "B. 銀河系", "C. 星間系", "D. 火星系"],
        "answer": "A"
    },
    {
        "question": "日本の首都はどこですか?",
        "options": ["A. 大阪", "B. 東京", "C. 名古屋", "D. 京都"],
        "answer": "B"
    },
    #とりあえず2問題だけ設定してますが必要に応じて増やしていきます!
]

3-5.エンドポイントの設定

ここではエンドポイントを設定していきます!
エンドポイントとは、フロントからバックエンド全体のURL/エンドポイントで設定した追加のURLにアクセスした時にどんな動作をするか定義したものです!

今回はバックのURL/question/{index}とおいうURLにアクセスした時に指定されたindexに設定されている問題や選択肢を返すように設定しています!
ここでのindexとは上記の問題設定で設定した順番です!
上から1問目はindex=0,2問目はindex=1のように設定されてます

@app.get("/question/{index}")
async def get_question(index: int):
    if index < len(questions):
        return questions[index]
    return {"question": "終了", "options": [], "answer": ""}
  • @app.get("/question/{index}"):リクエストを処理するエンドポイントです。URLのパスに {index} 変数があり、ここに問題のインデックスが入ります。
  • get_question(index: int): URLパスに入っている{index}を以下で定義されている関数内で使えるようにするための文言です。
  • if index < len(questions): 取得したが問題リストの範囲内かをチェックします。範囲内であれば、その問題を返します。(設定している問題数より大きいindexを指定した時に動作しないようにするためのもの!)
  • return questions[index]: リストからURLパスで指定されたインデックスの問題を返します。
  • return {"question": "終了", "options" : [], "answer": ""}: インデックスが範囲外の場合
    、「終了」のメッセージを返します。

上記のものが書けましたらバックエンドは完了です!

3-6.サーバーの起動

そうしましたらサーバーを起動してみましょう!
上記までの手順がうまくいっていればコマンドを入力するとサーバーが起動するはずです!

uvicorn main:app --reload

スクリーンショット 2024-09-16 2.56.14.png
ターミナルに上記のような表示が出てくれば完了です!

4.Reactアプリの設定

4-1.create-react-app のインストールとプロジェクトの作成

ここからはフロント部分を作成していきます。
こちらでもターミナルを使用するのですが、バックエンドと分けたいのでターミナルウィンドウの左上のアイコンの画面分割アイコンを押してもう一つウィンドウを作りましょう!これ以降はそのターミナルで操作します。
スクリーンショット 2024-09-16 2.56.28.png

以下のコマンドを入力し、今回使用するcreate-react-appのインストールとプロジェクトを作成していきます!
create-react-appを使用すると簡単にreactのアプリを作れるようになるライブラリです

npx create-react-app frontend
cd frontend

このコマンドを実行するとfrontendフォルダと、その中に様々なファイルやフォルダがインポートされていくと思います!
今回使用するものは以下のものなのでそれ以外は削除してしまいましょう!
スクリーンショット 2024-09-16 3.00.05.png

5.React アプリのコードを作成

ここからフロントエンドをReactにて作成していきます!
現在はコマンドの入力によってフロントエンドのディレクトリに移動していると思うのでその中にあるApp.jsというファイルを作成し、そこに記述していきましょう!
また同じように部分部分で解説していきます!

5-1.必要なライブラリのインポート

import React, { useState, useEffect } from "react";

ここでは今回必要となってくるライブラリをインポートします。
Reactは正確にはJavaScriptのライブラリの一種なのでインポートする必要があります。

  • useState: Reactのフックで、状態(データ)を管理するために使用します。
  • useEffect: Reactのフックで、副作用(データの取得やDOMの操作など)を扱うために使用します。

5-2.コンポーネントの定義

ここではコンポーネントを設定していきます
コンポーネントとはreactアプリで使用される「部品」のようなものでこれを作ることによってreactは様々なパーツを簡単に作成できます。

今回は簡単なプログラムのため一つのコンポーネントで作成するのですが、実際には色々なコンポーネントに分け管理をしやするします!

function App() {

5-3.状態の定義

  const [questionIndex, setQuestionIndex] = useState(0);
  const [question, setQuestion] = useState(null);
  const [selectedOption, setSelectedOption] = useState("");
  const [result, setResult] = useState(null);
  • questionIndex: 現在表示されている問題のインデックス(番号)を管理します。初期値は 0 に設定しておきます!(0番の問題から表示)
  • question: 現在の問題のデータを格納します。初期値は nullに設定します
  • selectedOption: ユーザーが選択した選択肢を格納します。初期値は空文字列("")に設定します
  • result: 答え合わせの結果(正解か不正解か)を格納します。初期値は nullに設定します

5-4.データの取得

  useEffect(() => {
    fetch(`http://127.0.0.1:8000/question/${questionIndex}`)
      .then(response => response.json())
      .then(data => {
        setQuestion(data);
        setResult(null);
        setSelectedOption("");
      })
      .catch(error => console.error("Error fetching question:", error));
  }, [questionIndex]);
  • useEffect: このフックは、コンポーネントがレンダリングされた後に副作用(データの取得など)を実行します。

  • fetch: 指定したURLからデータを取得します。この場合、http://127.0.0.1:8000/question/${questionIndex} というURLから現在の問題を取得します。

  • .then(response => response.json()): サーバーからの応答をJSON形式に変換します。

  • .then(data => { ... }): 取得したデータを使って、状態を更新します。

  • setQuestion(data): 取得した問題データを状態にセットします。

  • setResult(null): 問題を更新する際に結果をリセットします。

  • setSelectedOption(""): 選択肢をリセットします。

  • .catch(error => console.error("Error fetching question:", error)): データ取得に失敗した場合にエラーメッセージをコンソールに表示します。

  • [](依存配列): questionIndexが変わるたびにこのエフェクトが実行されることを示します。

5-5.答え合わせの処理

  const handleSubmit = () => {
    if (selectedOption === question.answer) {
      setResult("正解です!");
    } else {
      setResult("不正解です。");
    }
  };
  • handleSubmit: 「答え合わせ」ボタンがクリックされたときに呼び出される関数です。
  • if (selectedOption === question.answer): ユーザーが選択した選択肢が正しいかどうかを確認します。
  • setResult("正解です!"): 正解の場合に表示するメッセージを設定します。
  • setResult("不正解です。"): 不正解の場合に表示するメッセージを設定します。

5-6.次の問題に進む処理

ここでは「次の問題へ」ボタンを押した時の動作を設定します

  const handleNextQuestion = () => {
    setQuestionIndex((prevIndex) => prevIndex + 1);
  };
  • handleNextQuestion: 「次の問題へ」ボタンがクリックされたときに呼び出される関数です。
  • setQuestionIndex((prevIndex) => prevIndex + 1): 現在の問題のインデックスを1増やして、次の問題を表示します。

6-7.コンポーネントのレンダリング

ここでは実際にコンポーネントをレンダリング(表示)する時の動作を設定していきます!
まずは、問題データがまだ取得できていない時に「loading」という文字を表示するために以下のコードを開きましょう

  if (!question) return <div>Loading...</div>;

その後に続いて以下のコードを書いていきましょう!
実施にフロントを定義していくコードです!

  return (
    <div className="App">
      <h1>{question.question}</h1>
      <div>
        {question.options.map((option, index) => (
          <div key={index}>
            <label>
              <input
                type="radio"
                name="options"
                value={option}
                checked={selectedOption === option}
                onChange={(e) => setSelectedOption(e.target.value)}
              />
              {option}
            </label>
          </div>
        ))}
      </div>
      <button onClick={handleSubmit}>答え合わせ</button>

      {result && (
        <div>
          <h2>{result}</h2>
          <button onClick={handleNextQuestion}>次の問題へ</button>
        </div>
      )}
    </div>
  );
}
  • question.question: 現在の問題文を表示します。
  • question.options.map((option, index) => ( ... )): 選択肢のリストをループして、ラジオボタンを表示します。
  • <input type="radio" ... />: ラジオボタンを作成し、ユーザーの選択を管理します。
  • onChange={(e) => setSelectedOption(e.target.value)}: 選択肢が変更されたときに選択肢を状態に設定します。
  • <button onClick={handleSubmit}>答え合わせ</button>: 「答え合わせ」ボタンを作成し、クリック時に handleSubmit 関数を呼び出します。
  • {result && ( ... )}: result が存在する場合に、結果メッセージと「次の問題へ」ボタンを表示します。
  • <button onClick={handleNextQuestion}>次の問題へ</button>: 「次の問題へ」ボタンを作成し、クリック時に handleNextQuestion 関数を呼び出します。

6-8.Reactアプリの起動

上記のコードが書けましたらフロント部分に関する定義は完了です1
以下のコマンドを打って実際に起動しましょう!

npm start

起動してもまだバックエンドとの疎通をしていないため「Loading...」が表示されていると思います
スクリーンショット 2024-09-16 3.01.48.png

なのでこれから疎通をしていきましょう!

6.フロントエンドとバックエンドの接続

6-1CORSの設定(FastAPI側)

FastAPiでCORS設定を有効にするためにmain.pyに以下のコードを追加していきます。

まず、上の方にあるインポート宣言をしている部分に以下のコードを追加していきます。

from fastapi.middleware.cors import CORSMiddleware

次に、以下のコードを全体の真ん中あたりに追加していきます。これはCORS設定というもので、バックエンドにアクセスできるURLを設定します。
allow_originsの部分にフロントエンドのURLを設定いますが今回はローカルでの構築のため全く同じものを打つことで構築でいると思います!

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

7.実際に起動しましょう!

以上でのコードで起動部分に関しては完了です!

実際にブラウザを開いて確認してみましょう!
もしかしたら最新のものが読み込まれてない可能性があるので表示が変わっていない場合はリロードボタンを押してみてください
スクリーンショット 2024-09-16 3.02.05.png
以上のような表示がされれば成功です!

お疲れ様でした!

この先

この先に関しては

  • CSSを編集してデザインの部分を変更する。
  • アプリの機能を増やす
  • 問題の数を増やす
  • 実際に誰でもアクセスできるようにデプロイをしてみる

などのことを試してみてください!
実際に自分好みのアプリを作成することで学習の進みも早くなると思います!

最後に

以上問題アプリをローカルで構築する手順を説明してみました
いかがでしたでしょうか??

以下に実際に同じアプリを作ったgithubを公開しているのでもし行き詰まった時は見てみてください!
https://github.com/shonakamura000/HowTo_create_quiz_app.git

最後になりますが、実際にアプリを作ってみる楽しさを感じてもらえれば幸いです!
閲覧いただきありがとうございました!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0