3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Django-rest-frameworkとreact

Last updated at Posted at 2023-06-03

はじめに

drf と react の組み合わせをやっておきたかった。
参考サイトを再現しようとしたらあちこち破綻してて、結局DDDベースでゴリッゴリに修正したらすでに原型をとどめていない...:joy:
まぁキャッチアップとしてはいいか

参考サイト

Overview

  • django のプロジェクト名は drf_react_backend にする
    • 実際には config というプロジェクトを作成してプロジェクトフォルダ名を drf_react_backend に変更する
    • アプリケーション名は daily_report にする
    • venv名は venv311 にする
  • react のプロジェクト名は drf_react_frontend にする
chatgpt
僕: django rest framework と react を組み合わせてアプリをつくりたい。それぞれの最初のコマンドライン(セットアップ部分)をまとめて(django, react)

結局ドヤ顔で説明されて振り回された... :weary:

Django part

create root directory

console
mkdir drf_react_backend
cd drf_react_backend

venv

console
python -m venv venv311

create project

console
pip install django
pip install djangorestframework
pip install django-markdownx

django-admin startproject config .

create app

console
python manage.py startapp daily_report
drf_react_backend/config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
+   'rest_framework',
+   'markdownx',
+   'daily_report',
]
+REST_FRAMEWORK = {
+   'DEFAULT_PERMISSION_CLASSES': [
+       'rest_framework.permissions.AllowAny',
+   ]
+}

DEFAULT_PERMISSION_CLASSES は「誰にアクセスを許可するか」を指定する。

  • 今回は get しかない、かつ、取り回しの良さで AllowAny だが、これだと誰でも日報を追加したり削除できるので post リクエストなどがある場合は IsAuthenticatedOrReadOnly に変えるとよい。すると、get, head, options リクエストは誰にでも許可されるが、それ以外の post, delete などのリクエストは認証済みのユーザにしか許可されなくなる。

  • https://www.django-rest-framework.org/api-guide/permissions/

確認

console
python manage.py runserver

非公開情報を.envに移す(GitGuardian対策)

console
pip install django-environ
drf_react_backend/.env
SECRET_KEY=djaXXXXX
DEBUG=True
DB_ENGINE=django.db.backends.mysql
DB_NAME=drf_react_db
DB_USER=python
DB_PASSWORD=python123
drf_react_backend/config/settings.py
from pathlib import Path
+ import environ

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve(strict=True).parent.parent

+ # read at .env
+ env = environ.Env(DEBUG=(bool, False))
+ environ.Env.read_env(Path(BASE_DIR, '.env'))
+ DEBUG = env('DEBUG')  # read DEBUG at .env
+ SECRET_KEY = env('SECRET_KEY')  # read SECRET_KEY at .env

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/

- # SECURITY WARNING: keep the secret key used in production secret!
- SECRET_KEY = 'djaXXXXX'

- # SECURITY WARNING: don't run with debug turned on in production!
- DEBUG = True
    :

ここでいったん pycharm を再起動したり、コンソールの再起動をしないと引きずってえんえんとハマることになる

MySQL

console
pip install mysqlclient
drf_react_backend/config/settings.py
DATABASES = {
    'default': {
-       'ENGINE': 'django.db.backends.sqlite3',
-       'NAME': BASE_DIR / 'db.sqlite3',
+       'ENGINE': env('DB_ENGINE'),
+       'NAME': env('DB_NAME'),
+       'USER': env('DB_USER'),
+       'PASSWORD': env('DB_PASSWORD'),
    }
}
    :

mysqlにrootで入る

console
mysql -u root -p

create database

console
mysql> CREATE DATABASE drf_react_db DEFAULT CHARACTER SET utf8mb4;
       Query OK, 1 row affected (0.01 sec)

create user

console
mysql> CREATE USER 'python'@'localhost' IDENTIFIED BY 'python123';

create grant

console
mysql> grant CREATE, DROP, SELECT, UPDATE, INSERT, DELETE, ALTER, REFERENCES, INDEX on drf_react_db.* to python@localhost;

mysqlを出る

console
mysql> exit
console(作ったユーザで入れるか確認)
mysql -u python -p
console
mysql> exit

models.py

drf_react_backend/daily_report/models.py
from django.db import models
from markdownx.models import MarkdownxField


class Daily(models.Model):
    """
    univ: 大学の課題とか
    study: 勉強したこと
    other: その他
    first_meet: 初めて知ったこと
    wanna_do: やりたいこと
    summary: 1日のまとめ
    evaluation: 1日の評価(外部キー)
    isOpen: 公開/非公開
    """
    date = models.DateField()
    univ = MarkdownxField()
    study = MarkdownxField()
    other = MarkdownxField()
    first_meet = MarkdownxField()
    wanna_do = MarkdownxField()
    summary = MarkdownxField()
    evaluation = models.ForeignKey('Evaluation', on_delete=models.PROTECT)
    isOpen = models.BooleanField(default=True)

    def __str__(self):
        date_str = self.date.__format__('%Y/%m/%d')
        return date_str


class Evaluation(models.Model):
    evaluation = models.CharField(max_length=255)

    def __str__(self):
        return self.evaluation

migrate

console
python manage.py makemigrations daily_report
python manage.py migrate

create superuser

console
python manage.py createsuperuser
  ユーザー名 (leave blank to use 'yoshi'): 
  メールアドレス: yoshiXXXX@gmail.com
  Password: 
  Password (again):
  このパスワードは ユーザー名 と似すぎています。
  Bypass password validation and create user anyway? [y/N]: y
  Superuser created successfully.

fixtures

console
mkdir daily_report/fixtures
drf_react_backend/daily_report/fixtures/evaluation.json(新規)
[
  {
    "model": "daily_report.evaluation",
    "pk": 1,
    "fields": {
      "evaluation": "perfect"
    }
  },
  {
    "model": "daily_report.evaluation",
    "pk": 2,
    "fields": {
      "evaluation": "good"
    }
  },
  {
    "model": "daily_report.evaluation",
    "pk": 3,
    "fields": {
      "evaluation": "soso"
    }
  },
  {
    "model": "daily_report.evaluation",
    "pk": 4,
    "fields": {
      "evaluation": "bad"
    }
  }
]
drf_react_backend/daily_report/fixtures/daily.json(新規)
[
  {
    "model": "daily_report.daily",
    "pk": 1,
    "fields": {
      "date": "2023-05-01",
      "univ": "大学の課題1",
      "study": "勉強したこと1",
      "other": "その他1",
      "first_meet": "初めて知ったこと1",
      "wanna_do": "やりたいこと1",
      "summary": "1日のまとめ1",
      "evaluation": 1,
      "isOpen": true
    }
  },
  {
    "model": "daily_report.daily",
    "pk": 2,
    "fields": {
      "date": "2023-05-02",
      "univ": "大学の課題2",
      "study": "勉強したこと2",
      "other": "その他2",
      "first_meet": "初めて知ったこと2",
      "wanna_do": "やりたいこと2",
      "summary": "1日のまとめ2",
      "evaluation": 3,
      "isOpen": true
    }
  },
  {
    "model": "daily_report.daily",
    "pk": 3,
    "fields": {
      "date": "2023-05-03",
      "univ": "大学の課題3",
      "study": "勉強したこと3",
      "other": "その他3",
      "first_meet": "初めて知ったこと3",
      "wanna_do": "やりたいこと3",
      "summary": "1日のまとめ3",
      "evaluation": 2,
      "isOpen": true
    }
  },
  {
    "model": "daily_report.daily",
    "pk": 4,
    "fields": {
      "date": "2023-05-04",
      "univ": "大学の課題4",
      "study": "勉強したこと4",
      "other": "その他4",
      "first_meet": "初めて知ったこと4",
      "wanna_do": "やりたいこと4",
      "summary": "1日のまとめ4",
      "evaluation": 1,
      "isOpen": true
    }
  },
  {
    "model": "daily_report.daily",
    "pk": 5,
    "fields": {
      "date": "2023-05-05",
      "univ": "大学の課題5",
      "study": "勉強したこと5",
      "other": "その他5",
      "first_meet": "初めて知ったこと5",
      "wanna_do": "やりたいこと5",
      "summary": "1日のまとめ5",
      "evaluation": 1,
      "isOpen": true
    }
  }
]
console
python manage.py loaddata daily_report/fixtures/evaluation.json
python manage.py loaddata daily_report/fixtures/daily.json

urls.py

drf_react_backend/config/urls.py
"""
URL configuration for config project.

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
-from django.urls import path
+from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
+   path('daily_report/', include('daily_report.urls')),
+   path('markdownx/', include('markdownx.urls')),
]
drf_react_backend/daily_report/urls.py(新規)
from django.urls import path
from . import views

urlpatterns = [
    path('', views.DailyList.as_view()),
    path('<int:pk>/', views.DailyDetail.as_view()),
]
  • http://127.0.0.1:8000/daily/ というURLがきたら views.pyListDaily をみてね
  • http://127.0.0.1:8000/daily/1/ とか http://127.0.0.1:8000/daily/2/ というURLがきたら views.pyDetailDaily をみてね
  • http://127.0.0.1:8000/daily/study/ とか http://127.0.0.1:8000/daily/wanna_do/ というURLがきたらviews.pyCategoryDairy をみてね

ということ。つまりここでは http://127.0.0.1:8000/daily/ 以降のパスを指定している

serializers.py

Dailyモデル をベースに、取り扱いたい tuple の Serializer を作る

drf_react_backend/daily_report/serializers.py(新規)
from rest_framework import serializers

from .models import Daily


class DailyListSerializer(serializers.ModelSerializer):
    """
    日報の一覧用シリアライザ
    """
    evaluation = serializers.CharField(source='evaluation.evaluation')

    class Meta:
        model = Daily
        fields = ('id', 'date', 'evaluation')


class DailyDetailSerializer(serializers.ModelSerializer):
    """
    日報の詳細用シリアライザ
    """
    class Meta:
        model = Daily
        fields = ('id', 'univ', 'date', 'study', 'other', 'first_meet', 'wanna_do', 'summary')

views.py

JSONを返す処理を書いていく。Serializer を使うことで、データの詰め替えが(Djangoで Class-based View を使うときのように)スマートになっていることがわかる。のちにバリデーションしたときによりスマートさが際立つだろう。

drf_react_backend/daily_report/views.py
from django.http import Http404
from django.shortcuts import get_object_or_404
from rest_framework import generics, status
from rest_framework.response import Response

from .models import Daily
from .serializers import DailyListSerializer, DailyDetailSerializer


class DailyList(generics.ListAPIView):
    """
    日報の一覧
    isOpen が True になっているものを日付の降順で返す
    """
    queryset = Daily.objects.filter(isOpen=True).order_by('-date')
    serializer_class = DailyListSerializer


class DailyDetail(generics.RetrieveAPIView):
    """
    日報の詳細情報
    """
    queryset = Daily.objects.all()
    serializer_class = DailyDetailSerializer

    def get(self, request, *args, **kwargs):
        try:
            instance = get_object_or_404(self.get_queryset(), pk=self.kwargs['pk'])
            serializer = self.get_serializer(instance)
            return Response(serializer.data)
        except Http404:
            return Response(status=status.HTTP_404_NOT_FOUND)

確認

  • 日報の一覧を取得 http://127.0.0.1:8000/daily_report/image.png

  • idが 2 の日報の詳細情報を取得 http://127.0.0.1:8000/daily_report/2/image.png

  • date, univ というフィールドだけの一覧を取得 http://127.0.0.1:8000/daily_report/univ/image.png

setup CORS

今回のようにバックエンドをDjango、フロントをReactでやる場合、CORS(オリジン間リソース共有)の設定をする必要があります。DjangoとReactは異なるオリジンで動作するので、その間のHTTPリクエストを許可する必要があります

console
pip install django-cors-headers
drf_react_backend/config/settings.py
INSTALLED_APPS = [
      :
+   'corsheaders',
]
  :
MIDDLEWARE = [
+   'corsheaders.middleware.CorsMiddleware',
+   'django.middleware.common.CommonMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
+CORS_ORIGIN_WHITELIST = (
+   'http://localhost:3000', 
+)

.gitignore

  • gitignoreは、gitignore.ioのdjango用のものと migrationsフォルダを指定する

drf_react_backend/.gitignore
/daily_report/migrations/
    :
(最下行に追加: gitignore.ioのdjango用のもの)

React part

create root directory

console
mkdir drf_react_frontend
cd drf_react_frontend

create app

console
npx create-react-app . --template typescript

package.json

drf_react_frontend/package.json
{
    :
  "dependencies": {
      :
+   "react-router-dom": "^6.11.2",
+   "antd": "^5.6.1"
  },
    :
console
npm start

Testing

drf_react_frontend/package.json
{
    :
  "dependencies": {
      :
  },
+ "devDependencies": {
+   "eslint": "8.22.0"
+ },
    :
drf_react_frontend/src/services/sum.js
function sum (a, b) {
  return a + b
}
module.exports = sum
drf_react_frontend/src/services/__tests__/sum.test.js
const sum = require('../sum')

it('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3)
})

it('dataから!nullの値を取得できる条件文のテスト', () => {
  const data = {
    test1: 'a',
    test2: [],
    test3: ['1'],
    test4: '',
    test5: null
  }
  const expected = {
    test1: 'a',
    test3: ['1']
  }
  let actual = {}
  for (let [key, val] of Object.entries(data)) {
    if (val && val.length > 0) {
      actual[key] = val
    }
  }
  expect(actual).toEqual(expected)
})

console
npm test
 PASS  src/App.test.tsx
 PASS  src/services/__tests__/sum.test.js

 Test Suites: 2 passed, 2 total
 Tests:       3 passed, 3 total
 Snapshots:   0 total
 Time:        2.746 s
 Ran all test suites.

ぼく: あー、プロダクションコードがあるディレクトリに __tests__ フォルダを作る必要があるのね?


ChatGPT: はい、通常、プロダクションコードと対応するテストコードは同じディレクトリ構造を持つことが推奨されます。そのため、プロダクションコードがあるディレクトリ内に対応するテストコードを格納するために __tests__ ディレクトリを作成します。

これにより、プロダクションコードとテストコードが密接に関連しており、保守性が高まります。また、テストファイルをプロダクションコードと同じ場所に配置することで、開発者や他のチームメンバーがテストを見つけやすくなります。

ただし、必ずしも __tests__ ディレクトリを使用する必要はありません。Jestはデフォルトで、プロジェクト内のすべての *.test.js または *.spec.js という拡張子を持つファイルをテストファイルとして自動的に検出します。そのため、特定のディレクトリに制約されずにテストファイルを配置することも可能です。ただし、一貫性と可読性のために、一般的には __tests__ ディレクトリを使用することが推奨されます。

ページ構成をあたまで整理

  • 参考サイトを読み解いた結果、DDD風味にしてこんなイメージになった
    IMG20230613224149.jpg
    image.png

勝手に出来上がるファイルについて

App.tsx

全体的なレイアウト...か

ぼく: App.tsx ってどんな役割?


ChatGPT: App.tsx はReactアプリケーションのエントリーポイントとして機能します。このファイルは、アプリケーションのルートコンポーネントであり、アプリケーション全体のレイアウトやルーティングを定義するために使用されます。

App.tsx は通常、以下のような役割を果たします:

レイアウトの定義: App.tsx はアプリケーションの全体的なレイアウトを定義するために使用されます。例えば、ヘッダーやフッターなど、すべてのページで表示される共通のコンポーネントを配置することができます。

ルーティングの設定: App.tsx はルーティングライブラリ(例:React Router)を使用して、アプリケーションの異なるページ間のナビゲーションを設定するために使用されます。ルートコンポーネントとルートパスのマッピングや、URLパラメーターの処理などを行うことができます。

コンテキストの提供: App.tsx はReactのコンテキストを使用して、アプリケーション全体で共有するデータや状態を提供するために使用されることもあります。例えば、認証情報やテーマの設定など、多くのコンポーネントで利用される共有データを管理する場合に使用できます。

App.tsx はReactアプリケーションの中心的な役割を果たすファイルであり、その他のコンポーネントや機能はこのファイルを基準にして組み込まれます。

index.tsx

App.tsx をトリガーするもの...か

ぼく: index.tsx はなに?


ChatGPT: index.tsx はReactアプリケーションのエントリーポイントであり、アプリケーションの起点となるファイルです。このファイルは、Reactの仮想DOM (Virtual DOM) をブラウザの実際のDOMにマウントするための役割を担っています。

具体的には、index.tsx は以下のような役割を果たします:

ルートコンポーネントのレンダリング: index.tsx はアプリケーションのルートコンポーネントをレンダリングするために使用されます。通常、ルートコンポーネントは <App /> のような形式で指定され、アプリケーション全体のコンポーネントツリーの起点となります。

ブラウザのDOMへのマウント: index.tsx はReactの仮想DOMをブラウザの実際のDOMにマウントする役割を持ちます。ReactDOM.render() メソッドを使用して、ルートコンポーネントを指定したDOM要素にレンダリングします。

ホットリロードの設定(開発モードの場合): 開発モードで index.tsx を使用している場合、ホットリロードの設定も行われます。ファイルの変更を検知し、自動的にアプリケーションを再描画することができます。

index.tsx はReactアプリケーションのエントリーポイントであり、アプリケーションの起動時に最初に実行されるファイルです。そのため、アプリケーションの初期化や構成の設定など、重要な役割を果たしています。

ぼく: index.tsx が最初に呼ばれて、そのときに App を呼ぶのでつぎに App.tsx が呼ばれる感じか。djangoでいう base.htmlindex.html の感じに似ている。


ChatGPT: はい、おっしゃる通りです。 index.tsx は最初に呼び出され、その中で <App /> コンポーネントがレンダリングされます。その後、Reactは <App /> コンポーネント内のコンポーネントツリーを構築し、最終的にブラウザに表示されます。

react-app-env.d.ts

環境設定ファイル...か

ぼく: react-app-env.d.ts はなに?


ChatGPT: react-app-env.d.ts は、Reactアプリケーションの環境設定ファイルです。このファイルは、TypeScriptの型定義やグローバルな型拡張を含むために使用されます。

通常、Reactアプリケーションでは、.tsx 拡張子のファイル内でTypeScriptを使用して開発を行います。しかし、プロジェクトのルートディレクトリに react-app-env.d.ts ファイルが存在すると、TypeScriptコンパイラはそのファイルを自動的に読み込み、その中に定義された型情報を利用できるようになります。

react-app-env.d.ts ファイルは、主に以下のような目的で使用されます:

プロジェクト固有の型定義: プロジェクト内で利用するカスタムな型定義やインターフェースの定義を含むことがあります。これにより、アプリケーション全体で共通の型情報を利用することができます。

グローバルな型拡張: 既存のライブラリやフレームワークの型定義を拡張するための型定義を含むことがあります。これにより、外部ライブラリの型情報を補完したり、より具体的な型情報を提供したりすることができます。

react-app-env.d.ts ファイルは、通常、自動生成されます。Reactアプリケーションを作成するときに、Create React App などのツールが自動的に生成してくれることがあります。ファイルの中身はプロジェクトの具体的な要件によって異なる場合があります。

reportwebvitals.ts

パフォーマンス計測関連...か

ぼく: reportwebvitals.ts はなに?


ChatGPT: reportWebVitals.tsは、ReactアプリケーションでのWeb Vitals(Webのパフォーマンス指標)の計測とレポートを行うためのファイルです。

Web Vitalsは、ユーザーエクスペリエンスの測定に役立つ重要なパフォーマンス指標のセットです。これには、ページの読み込み時間、反応性、レンダリングの遅延などが含まれます。reportWebVitals.tsファイルは、これらのパフォーマンス指標を計測し、レポートするための関数を提供します。

通常、reportWebVitals.tsファイルは、Create React App などのツールによってプロジェクトの初期設定時に自動的に生成されます。このファイルには、reportWebVitalsという関数が含まれており、この関数を使用してWeb Vitalsの計測とレポートが行われます。

reportWebVitals関数は、web-vitalsというパッケージを使用して、ブラウザのパフォーマンスAPIからWeb Vitalsのデータを収集します。収集されたデータは、開発者が指定した方法でレポートされます。デフォルトでは、コンソールにログが出力されますが、開発者は必要に応じてこの挙動をカスタマイズすることもできます。

reportWebVitals.tsファイルは、Web Vitalsの計測とレポートを行うための重要なツールであり、パフォーマンスの改善やユーザーエクスペリエンスの向上に役立ちます。

App.tsx

主にルーティングの設定

drf_react_frontend/src/App.tsx
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import GeneralHeader from './pages/GeneralHeader';
import GeneralHomeView from './pages/GeneralHomeView';
import HomeView from './pages/daily_report/HomeView';
import DetailView from './pages/daily_report/DetailView';
import ProfileView from './pages/profile/ProfileView';
import Page404 from "./pages/Page404";

const App: React.FC = () => {
  return (
    <div>
      <Router>
        <GeneralHeader />
        <div>
          <Routes>
            <Route path="/" element={<GeneralHomeView />} />
            <Route path="/daily_report" element={<HomeView />} />
            <Route path="/daily_report/:id" element={<DetailView />} />
            <Route path="/profile" element={<ProfileView />} />
            <Route path="*" element={<Page404 />} />
          </Routes>
        </div>
      </Router>
    </div>
  );
};

export default App;

DailyReport.tsx

  • djangoの向き先を取りまとめる
drf_react_frontend/src/daily_report/api/DailyReport.tsx
// apiにアクセスして日記のリストを取得します
export const getReportList = async () => {
  const res = await fetch('http://localhost:8000/daily_report/', {
    method: 'GET',
  });
  return await res.json();
};

// apiにアクセスして日記の詳細を取得します
export const getReportDetail = async (id: string) => {
    const res = await fetch( `http://localhost:8000/daily_report/${id}`, {
      method: 'GET',
    });
    if (res.ok) {
      return await res.json();
    } else {
      // 検索結果 0件
      throw new Error(`id: ${id} では日記を取得できませんでした`);
    }
};
  • http://localhost:3000/ というパスが指定されたら Top を表示
  • http://localhost:3000/daily というパスが指定されたら DailyTop を表示
  • http://localhost:3000/daily/3 とかのパスが指定されたら DailyDetail を表示
  • http://localhost:3000/daily/category/univ とかのパスが指定されたら CategoryView を表示
  • どれにも当てはまらなかったら not found... と表示

GeneralHeader.tsx

gpt: Reactコンポーネントを含むファイルは .tsx ファイルを使用し、それ以外のTypeScriptファイルは .ts ファイルを使用します。


まぁ .ts にするとIDEがエラー出すはず

gpt: React.FC を使用することで、関数コンポーネントのpropsの型を指定することができます。

drf_react_frontend/src/pages/GeneralHeader.tsx
import React from 'react';
import { Link } from 'react-router-dom';

const GeneralHeader: React.FC = () => {
  return(
    <div>
      <Link to='/'>Top</Link>
      <ul>
        <li><Link to='/daily_report'>日報</Link></li>
        <li><Link to='/profile'>プロフィール</Link></li>
      </ul>
    </div>
  )
}

export default GeneralHeader

GeneralHomeView.tsx

drf_react_frontend/src/pages/GeneralHomeView.tsx
import React from 'react';

const GeneralHomeView: React.FC = () => {
  return (
    <div>
      <h1>総合Topです</h1>
      <p>このアプリケーションは...</p>
      <ul>
        <li>あれができます</li>
        <li>これができます</li>
      </ul>
    </div>
  );
};

export default GeneralHomeView

HomeView.tsx

  • 記事のリストを持ってきて map 関数でくるくると Conponent に引き渡している
drf_react_frontend/src/pages/daily_report/HomeView.tsx
import React, { useState, useEffect } from 'react';
import { getReportList } from '../../daily_report/api/DailyReport';
import { Content } from '../../daily_report/components/home/Content';
import { HomeContentEntity } from "../../daily_report/domain/HomeContentEntity";

const HomeView = () => {
  const [entities, setEntities] = useState<HomeContentEntity[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    getReportList()
      .then((values: HomeContentEntity[]) => {
        setEntities(values);
        setLoading(false);
      })
  }, []);

  return (
    <div>
      { loading ? (
        <h1>loading...</h1>
      ) : (
        <div>
          { entities.map( entity => <Content key={ entity.id } { ...entity } /> ) }
        </div>
      ) }
    </div>
  );
};

export default HomeView

DetailView.tsx

  • url から id を取得して 単一の記事を取得して detail/content に引き渡している
drf_react_frontend/src/pages/daily_report/DetailView.tsx
import React, { useState, useEffect } from 'react';
import { getReportDetail } from "../../daily_report/api/DailyReport";
import { Content } from "../../daily_report/components/detail/Content";
import { DetailContentEntity } from "../../daily_report/domain/DetailContentEntity";
import { useParams } from "react-router-dom";

const DetailView: React.FC = () => {
  const [entity, setEntity] = useState<DetailContentEntity>({} as DetailContentEntity)
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  // get url param
  const { id } = useParams<{ id: string }>();

  useEffect(() => {
    if (id) {
      getReportDetail(id)
        .then((values: DetailContentEntity) => {
          setEntity(values)
          setLoading(false)
        })
        .catch((e: Error) => {
          setError(e.message);
          setLoading(false);
        });
    }
  },[id])

  if (error) {
    return <h1>{error}</h1>;
  }

  return (
    <div>
      { loading ? (
        <h1>loading....</h1>
      ) : (
        <Content key={ entity.id } { ...entity } />
      ) }
    </div>
  );
};

export default DetailView

ProfileView.tsx

drf_react_frontend/src/pages/profile/ProfileView.tsx
import React from 'react';

const ProfileView: React.FC = () => {
  return (
    <div>
      <h1>Profileです</h1>
    </div>
  );
};

export default ProfileView

Page404.tsx

drf_react_frontend/src/pages/Page404.tsx
import React from 'react';
import { Link } from 'react-router-dom';

const Page404 = () => {
  return (
    <>
      <h1>404 NOT FOUND</h1>
      <p>お探しのページが見つかりませんでした。</p>
      <Link to="/">Topに戻る</Link>
    </>
  );
};

export default Page404;

home/Content.tsx

drf_react_frontend/src/daily_report/components/home/Content.tsx
import React from 'react';
import { Link } from 'react-router-dom';

import img1 from '../../images/home/perfect.png';
import img2 from '../../images/home/good.png';
import img3 from '../../images/home/soso.png';
import img4 from '../../images/home/bad.png';
import { HomeContentEntity } from "../../domain/HomeContentEntity";

export const Content = (entity: HomeContentEntity) => {
  let evaluation_img;
  if (entity.evaluation === 'perfect') {
    evaluation_img = img1;
  } else if (entity.evaluation === 'good') {
    evaluation_img = img2;
  } else if (entity.evaluation === 'soso') {
    evaluation_img = img3;
  } else {
    evaluation_img = img4;
  }

  return (
    <div>
      <Link to={ `${ entity.id }` } >
        <h2>{ entity.date }</h2>
      </Link>
      <img src={ evaluation_img } alt="evaluation" />
    </div>
  );
};

detail/Content.tsx

drf_react_frontend/src/daily_report/components/detail/Content.tsx
import React from 'react';

import { DetailContentEntity } from "../../domain/DetailContentEntity";

export const Content = (entity: DetailContentEntity) => {
  return (
    <div>
      <h1>{ entity.date }</h1>
      <h2>大学のこと</h2>
      <div>{ entity.univ }</div>
      <h2>勉強</h2>
      <div>{ entity.study }</div>
      <h2>その他</h2>
      <div>{ entity.other }</div>
      <h2>初めましてだったこと</h2>
      <div>{ entity.first_meet }</div>
      <h2>やりたいこと</h2>
      <div>{ entity.wanna_do }</div>
      <h2>1日のまとめ</h2>
      <div>{ entity.summary }</div>
    </div>
  );
};

HomeContentEntity.tsx

drf_react_frontend/src/daily_report/domain/HomeContentEntity.ts
export type HomeContentEntity = {
  id: string,
  date: string,
  evaluation: string,
};

DetailContentEntity.tsx

drf_react_frontend/src/daily_report/domain/DetailContentEntity.ts
export type DetailContentEntity = {
  id: string,
  date: string,
  univ: string,
  study: string,
  other: string,
  first_meet: string,
  wanna_do: string,
  summary: string,
};

確認

image.png
image.png
image.png
image.png

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?