はじめに
drf と react の組み合わせをやっておきたかった。
参考サイトを再現しようとしたらあちこち破綻してて、結局DDDベースでゴリッゴリに修正したらすでに原型をとどめていない...
まぁキャッチアップとしてはいいか
参考サイト
Overview
- django のプロジェクト名は
drf_react_backend
にする- 実際には
config
というプロジェクトを作成してプロジェクトフォルダ名をdrf_react_backend
に変更する - アプリケーション名は
daily_report
にする - venv名は
venv311
にする
- 実際には
- react のプロジェクト名は
drf_react_frontend
にする
僕: django rest framework と react を組み合わせてアプリをつくりたい。それぞれの最初のコマンドライン(セットアップ部分)をまとめて(django, react)
結局ドヤ顔で説明されて振り回された...
Django part
create root directory
mkdir drf_react_backend
cd drf_react_backend
venv
python -m venv venv311
create project
pip install django
pip install djangorestframework
pip install django-markdownx
django-admin startproject config .
create app
python manage.py startapp daily_report
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/
確認
python manage.py runserver
非公開情報を.envに移す(GitGuardian対策)
pip install django-environ
SECRET_KEY=djaXXXXX
DEBUG=True
DB_ENGINE=django.db.backends.mysql
DB_NAME=drf_react_db
DB_USER=python
DB_PASSWORD=python123
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
pip install mysqlclient
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で入る
mysql -u root -p
create database
mysql> CREATE DATABASE drf_react_db DEFAULT CHARACTER SET utf8mb4;
Query OK, 1 row affected (0.01 sec)
create user
mysql> CREATE USER 'python'@'localhost' IDENTIFIED BY 'python123';
create grant
mysql> grant CREATE, DROP, SELECT, UPDATE, INSERT, DELETE, ALTER, REFERENCES, INDEX on drf_react_db.* to python@localhost;
mysqlを出る
mysql> exit
mysql -u python -p
mysql> exit
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
python manage.py makemigrations daily_report
python manage.py migrate
create superuser
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
mkdir daily_report/fixtures
[
{
"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"
}
}
]
[
{
"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
}
}
]
python manage.py loaddata daily_report/fixtures/evaluation.json
python manage.py loaddata daily_report/fixtures/daily.json
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')),
]
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.py
のListDaily
をみてね -
http://127.0.0.1:8000/daily/1/
とかhttp://127.0.0.1:8000/daily/2/
というURLがきたらviews.py
のDetailDaily
をみてね -
http://127.0.0.1:8000/daily/study/
とかhttp://127.0.0.1:8000/daily/wanna_do/
というURLがきたらviews.py
のCategoryDairy
をみてね
ということ。つまりここでは http://127.0.0.1:8000/daily/
以降のパスを指定している
serializers.py
Dailyモデル をベースに、取り扱いたい tuple の Serializer を作る
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
を使うときのように)スマートになっていることがわかる。のちにバリデーションしたときによりスマートさが際立つだろう。
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)
確認
setup CORS
今回のようにバックエンドをDjango、フロントをReactでやる場合、CORS(オリジン間リソース共有)の設定をする必要があります。DjangoとReactは異なるオリジンで動作するので、その間のHTTPリクエストを許可する必要があります
pip install django-cors-headers
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フォルダを指定する
/daily_report/migrations/
:
(最下行に追加: gitignore.ioのdjango用のもの)
React part
create root directory
mkdir drf_react_frontend
cd drf_react_frontend
create app
npx create-react-app . --template typescript
package.json
{
:
"dependencies": {
:
+ "react-router-dom": "^6.11.2",
+ "antd": "^5.6.1"
},
:
npm start
Testing
{
:
"dependencies": {
:
},
+ "devDependencies": {
+ "eslint": "8.22.0"
+ },
:
eslint は 8.22
がいいみたい。
ESLint: TypeError: this.libOptions.parse は関数ではありません
function sum (a, b) {
return a + b
}
module.exports = sum
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)
})
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__
ディレクトリを使用することが推奨されます。
ページ構成をあたまで整理
勝手に出来上がるファイルについて
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.html
と index.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
主にルーティングの設定
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の向き先を取りまとめる
// 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の型を指定することができます。
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
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 に引き渡している
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 に引き渡している
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
import React from 'react';
const ProfileView: React.FC = () => {
return (
<div>
<h1>Profileです</h1>
</div>
);
};
export default ProfileView
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
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
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
export type HomeContentEntity = {
id: string,
date: string,
evaluation: string,
};
DetailContentEntity.tsx
export type DetailContentEntity = {
id: string,
date: string,
univ: string,
study: string,
other: string,
first_meet: string,
wanna_do: string,
summary: string,
};