📑

DRF と React で ToDo リストを作る

2023/10/23に公開

フロントエンドに React、バックエンドに DRF を用いた ToDo リストを作る。2 つの連携を理解することが目的なので機能は最小限にする。

かなり雑なコードだが個人の勉強ということで...

DRF

プロジェクトのフォルダを作る

  • プロジェクト全体のフォルダとして、任意の場所にTODOLISTフォルダを作る

バックエンド用のフォルダを作る

  • バックエンド用のフォルダとしてTODOLIST内にbackendフォルダを作る
  • VSCode を起動>ファイルフォルダーを開くbackendを選択する
  • VSCode のジャンプリストでbackendをピン止めすると、後で開くときに便利(ジャンプリスト:Windows でタスクバーのアイコンを右クリックすると出るリスト)

仮想環境をつくる

仮想環境によって、パッケージの依存環境を OS 本体と分離できるらしい?

  • 仮想環境をつくる。(仮想環境名)には任意の名前を入力する。ここではenv

    > python -m venv (仮想環境名)
    
  • 仮想環境を有効化する。有効化されるとコマンドラインの先頭に環境名が表示される(env)>

    > env\Scripts\activate
    
  • (今は使わない)仮想環境を出るとき

    (env)> deactivate
    
  • (今はしない)仮想環境が必要なくなったら、仮想環境をつくるときにできる env ディレクトリを削除する

DRF インストール

  • Django、djangorestframework をインストールする
    (env)> pip install django
    (env)> pip install djangorestframework
    

Django プロジェクト作成

  • プロジェクトを作成する。(プロジェクト名)には任意の名前を入力する。ここではproject

    (env)> django-admin startproject (プロジェクト名)
    
  • 作成したプロジェクトに移動

    (env)> cd project
    
  • 開発用サーバーを起動する。

    (env)> python manage.py runserver
    
  • http://localhost:8000/にアクセスし、ロケット打ち上げのページが表示されたら完了。

アプリケーション作成

  • アプリケーションを作成する。(アプリ名)には任意の名前を入力する。ここではtodo

    (env)> python manage.py startapp (アプリ名)
    

Django 設定変更

TODOLIST\backend\project\setting.pyを編集する

  • 言語設定を変更

    LANGUAGE_CODE = 'ja'
    
  • タイムゾーンを変更

    TIME_ZONE = 'Asia/Tokyo'
    
  • 先ほど作成したアプリケーションと DRF を登録する

    INSTALLED_APPS = [
      "django.contrib.admin",
      "django.contrib.auth",
      "django.contrib.contenttypes",
      "django.contrib.sessions",
      "django.contrib.messages",
      "django.contrib.staticfiles",
      "rest_framework", # 追加
      "todo", # 追加
    ]
    

Model を作成する

TODOLIST\backend\project\todo\models.pyを編集する

from django.db import models


class ToDo(models.Model):
    todo = models.CharField(max_length=64)

migration

(env)> python manage.py makemigrations
(env)> python manage.py migrate

Serializer を作成する

  • Serializer:入力したデータの形式をチェックしたり、出力するデータの形式をフォーマットしたりする
  • TODOLIST\backend\project\project\todoフォルダにserializer.pyファイルを作成し、以下を入力する。
from rest_framework import serializers

from .models import User

class ToDoSerializer(serializers.ModelSerializer):
    class Meta:
        model = ToDo
        fields = (
            "id",
            "todo",
        )
  • Views にて serializer_class に使用する serializer を記述すると使えるようになる

Views を作成する

todo\views.pyを編集

from rest_framework import viewsets

from .models import ToDo
from .serializer import ToDoSerializer


class ToDoViewSet(viewsets.ModelViewSet):
    queryset = ToDo.objects.all()
    serializer_class = ToDoSerializer

URL を設定する

project\urls.pyを編集

from django.contrib import admin
from django.urls import include, path
from todo.urls import router

urlpatterns = [
  path("admin/", admin.site.urls),
  path("api/", include(router.urls))
]

todoフォルダにurls.pyファイルをつくり、以下を入力

from rest_framework import routers
from todo.views import ToDoViewSet

router = routers.DefaultRouter()
router.register(r"todo", ToDoViewSet)

サーバー起動

(env)> python manage.py runserver
  • http://localhost:8000/api/todo/にアクセスすると、to do listと書かれたページが表示される。ここからデータを登録できる。
  • とりあえずバックエンド側の準備はだいたい終わり

今のフォルダ構成

TODOLIST
└── backend
    ├── env
    └── project
        ├── project
        │   ├── __pycache__
        │   ├── __init__.py
        │   ├── asgi.py
        │   ├── settings.py
        │   ├── urls.py
        │   └── wsgi.py
        ├── todo
        │   ├── __pycache__
        │   ├── migrations
        │   ├── __init__.py
        │   ├── admin.py
        │   ├── apps.py
        │   ├── models.py
        │   ├── serializer.py
        │   ├── tests.py
        │   ├── urls.py
        │   └── views.py
        ├── db.sqlite3
        └── manage.py

React 設定

フロントエンド用のフォルダを作る

  • VSCode で新しいウィンドウを開く>ファイルフォルダを開くTODOLISTフォルダを選択

React プロジェクト作成

  • React を始めるために以下のコードを入力する。プロジェクト名には任意の名前を入力する(ここではfrontend)。--template typescriptをつけると TypeScript が使えるようになる
    > npx create-react-app (プロジェクト名) --template typescript
    
  • Happy hacking!と表示されたら完了。TODOLIST\frontend内にいろんなファイルができる
  • フォルダを開きなおそう。VSCode のファイルフォルダを開くfrontendフォルダを選択
  • こちらもfrontendを VSCode のジャンプリストにピン止めすると便利

開発環境立ち上げ

  • 以下のコードを入力し、開発環境を立ち上げる
    > cd frontend // プロジェクト名
    > npm start // ページが開く
    

コードを整理する

  • デフォルトのファイルでごちゃごちゃしているので、下記以外は消してしまおう

    • frontend\public
      • index.html
    • frontend\src
      • index.tsx
      • App.tsx
      • react-app-env.d.ts
    • frontend直下のファイルは残す(package.jsonとか)
  • react-app-env.d.tsファイルがあると React や css(scss) modules が global で使用可能になるらしい(参考

  • public\index.htmlを以下のように変更する

    <!DOCTYPE html>
    <html lang="ja">
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="description" content="ToDo List" />
        <title>ToDo List</title>
      </head>
      <body>
        <div id="root"></div>
      </body>
    </html>
    
  • src\index.tsxを以下のように変更する

    import React from "react";
    import ReactDOM from "react-dom/client";
    import App from "./App";
    
    const root = ReactDOM.createRoot(
      document.getElementById("root") as HTMLElement
    );
    root.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    
  • src\App.tsxをとりあえず以下のように変更する(後でまた変更する)

    function App() {
      return <></>;
    }
    
    export default App;
    

今のフォルダ構成

TODOLIST
├── backend
│   ├── env
│   └── project
│       ├── project
│       │   ├── __pycache__
│       │   ├── __init__.py
│       │   ├── asgi.py
│       │   ├── settings.py
│       │   ├── urls.py
│       │   └── wsgi.py
│       ├── todo
│       │   ├── __pycache__
│       │   ├── migrations
│       │   ├── __init__.py
│       │   ├── admin.py
│       │   ├── apps.py
│       │   ├── models.py
│       │   ├── serializer.py
│       │   ├── tests.py
│       │   ├── urls.py
│       │   └── views.py
│       ├── db.sqlite3
│       └── manage.py
└── frontend
    ├── node_modules
    ├── public
    │   └── index.html
    ├── src
    │   ├── App.tsx
    │   ├── index.tsx
    │   └── react-app-env.d.ts
    ├── .gitignore
    ├── package-lock.json
    ├── package.json
    ├── README.md
    └── tsconfig.json

XMLHttpRequest

  • フロントとサーバーのやりとりにはXMLHttpRequestなるものを使う

  • 手順(参考

    • 1.XMLHttpRequestを作成する。
      const xhr = new XMLHttpRequest();
      
    • 2.初期化
      xhr.open(メソッド, URL);
      
      メソッドはGETとかPOSTとかDELETEとか。
    • 3.送る
      xhr.send(body);
      
      GET だったら body は空だが、POST は body を使う(xhr.send("todo=洗濯")とか)
  • src\App.tsxを以下のように変更する

    import { useEffect, useState } from "react";
    
    function App() {
      const [toDo, setTodo] = useState<string>("");
      const [data, setData] = useState<{ id: number; todo: string }[]>([]);
    
      const onChangeToDo = (e: React.ChangeEvent<HTMLInputElement>) => {
        setTodo(e.target.value);
      };
    
      const xhr = new XMLHttpRequest();
      // データ取得
      const getData = () => {
        xhr.open("GET", "http://localhost:8005/api/todo/");
        xhr.send();
        xhr.onloadend = () => {
          setData(JSON.parse(xhr.responseText));
        };
      };
    
      // データ送信
      const postData = () => {
        xhr.open("POST", "http://localhost:8005/api/todo/");
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        xhr.send(`todo=${toDo}`);
        xhr.onloadend = () => {
          getData(); // 送信したらデータを取得
          setTodo(""); // 入力欄を空にする
        };
      };
    
      // データ削除
      const deleteData = (e: React.MouseEvent<HTMLButtonElement>) => {
        xhr.open(
          "DELETE",
          `http://localhost:8005/api/todo/${e.currentTarget.value}/`
        );
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        xhr.send();
        xhr.onloadend = () => {
          getData(); // 削除したらデータを取得
        };
      };
    
      // 初回読み込み
      useEffect(() => {
        getData();
      }, []);
    
      return (
        <>
          <div>ToDo List</div>
          <input value={toDo} onChange={onChangeToDo} />
          <button onClick={() => postData()}>保存</button>
          {Object.values(data).map((data) => (
            <div key={data.id}>
              <button value={data.id} onClick={deleteData}>
                削除
              </button>
              {data.id}-{data.todo}
            </div>
          ))}
        </>
      );
    }
    
    export default App;
    

フロント側の設定は終了。これで終わりと言いたいところだが、最後に DRF で下記を設定する。

再び DRF

CORS 設定

参考:https://qiita.com/sand/items/80a67da0a44b042f0bc3

CORS なる設定を行わないと、フロントからのリクエスト時に以下のエラーが出る
Access to XMLHttpRequest at 'http://localhost:8000/todo/users' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

  • django-cors-headersをインストールする

    (env)> pip install django-cors-headers
    
  • setting.pyに以下を追加する

    
    INSTALLED_APPS = [
        ...
        "django.contrib.staticfiles",
        "rest_framework",
        "todo",
        "corsheaders", # 追加
    ]
    
    MIDDLEWARE = [
        "corsheaders.middleware.CorsMiddleware",  # 追加 一番上に置く
        "django.middleware.security.SecurityMiddleware",
        ...
    ]
    
    ...
    
    # 追加
    CORS_ORIGIN_WHITELIST = [
        "http://localhost:3000",
    ]
    
    
  • 保存後、フロントからリクエストしてもエラーが出なくなる

  • 追加してもエラーが出るときは、ブラウザのキャッシュを消去する

    • (Chrome)ブラウザの更新ボタン長押し>キャッシュの消去とハード再読み込みをクリック

感想

簡単な実装だが、一からやると時間がかかった。
業務の実装はたいてい複雑だが、基本を押さえることで理解できるはず

その他の学び

  • パス名の記述(参考
    • UNIX 系 OS:スラッシュ(/)で区切る
    • Windows:バックスラッシュ(\)で区切る
  • VSCode でタブをピン留め
    • (タブを選択した状態で)Ctrl + KShift + Enter
  • フォルダ構成図
    • 記号の読み
      • ├:たてみぎ
      • │:たて
      • ─:よこ
      • └:ひだりした
    • 「├── 」、「└── 」のように繋げた状態で単語登録したら打つ手間が省ける
  • Django、プロジェクトフォルダ名と設定フォルダ名を別々に命名できる技があるらしい(参考

全体的な参考

Django と React で自分用日報アプリを作る~①Django 編~
React+DjangoRESTFramwork で SPA の ToDo アプリをつくる

Discussion