DRF と React で ToDo リストを作る
フロントエンドに 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.送るGET だったら body は空だが、POST は body を使う(
xhr.send(body);
xhr.send("todo=洗濯")
とか)
- 1.
-
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)ブラウザの更新ボタン長押し>
キャッシュの消去とハード再読み込み
をクリック
- (Chrome)ブラウザの更新ボタン長押し>
感想
簡単な実装だが、一からやると時間がかかった。
業務の実装はたいてい複雑だが、基本を押さえることで理解できるはず
その他の学び
- パス名の記述(参考)
- UNIX 系 OS:スラッシュ(/)で区切る
- Windows:バックスラッシュ(\)で区切る
- VSCode でタブをピン留め
- (タブを選択した状態で)
Ctrl
+K
→Shift
+Enter
- (タブを選択した状態で)
- フォルダ構成図
- 記号の読み
- ├:たてみぎ
- │:たて
- ─:よこ
- └:ひだりした
- 「├── 」、「└── 」のように繋げた状態で単語登録したら打つ手間が省ける
- 記号の読み
- Django、プロジェクトフォルダ名と設定フォルダ名を別々に命名できる技があるらしい(参考)
全体的な参考
Django と React で自分用日報アプリを作る~①Django 編~
React+DjangoRESTFramwork で SPA の ToDo アプリをつくる
Discussion