Next.js ver.15 ベストプラクティス
開発
ルーティング
- app/配下のみに集約し、component/は同階層に分ける
app/ | |-page.tsx | |... | components/ | ...
コンポーネント
- なるべくデフォルトのServerComponentを使い、hooksを使う時はClientComponentを使う
- ClientComponentの子は全てClientComponentとなるため、ClientComponentは末端に置くのがベスト
データフェッチ
- Server Componentから非同期で同期的にデータを取得する場合は、データを取得後に画面が描画される。よってClient Componentを先に表示させ、Server ComponentはSuspenseでラップし、Loadingなどを表示させるのがベスト
<ClientComponent /> <Suspense fallback(<div>Loading...</div>)> <ServerComponent /> </Suspense>
- まずはServerComponentを使うのがベスト
- 初回読み込み早い
- セキュア
- キャッシュ利用など
- リアルタイムはClientComponentを使う
キャッシュ
- 実装箇所により使い分ける
レンダリング
- ServerComponentからClientComponentは呼べるが逆は不可
- ServerComponentでデータ取得がないとSSG、あるとSSR
- 実装箇所により使い分ける
----------GPTで生成----------
| いつ | どこで | |
|---|---|---|
| SSG | ビルド時 | サーバ |
| SSR | リクエスト時 | サーバ |
| ISR | ビルド時 | サーバ |
| CSR | ブラウザでJSが実行されたとき | クライアント |
- ServerComponentでCSRは不可だが、ClientComponentでSSRなどは可
- e.g. LikeButton だけが "use client" でClient Component、他はServer Componentとなり、HTMLはサーバで生成、JSでボタンだけ後から動く→ハイドレーション
----------GPTで生成----------
- PPRの登場でページ単位からUI単位のレンダリングになる予定
メタデータ
- 基本設定を抑える
- ※クローラー対策のため閉じた環境では対象外
ミドルウェア
- URLリクエスト前に実行される関数のこと(認証とか)
- 簡易的な認可チェックなどに使う
参考
Django REST Frameworkベストプラクティス
JavaScriptベストプラクティス
開発
比較
- 比較は===、!==を使う(==はあいまい比較となるため不可)
if (test1 === test2) {}
関数
- 関数はアロー関数で書く
const test = () => {}
オブジェクト
- オブジェクトはobj.keyで書く
const obj = { test: "test1" } obj.test
- そのオブジェクトがどのようなプロパティ(オブジェクトのkey, valueのこと)を持っているかがわかりにくくなるため、作成後に新しいプロパティは追加しない
配列
- 配列末尾へのアクセスは[-1]でなく[array.length - 1]を使う
array[array.length - 1]
- 配列は非破壊的なメソッドを使う
this
- thisは状況により参照先が変わるためメソッドではない通常の関数においてはthisは使わない
- メソッドのthisはアロー関数のみで使い、スコープチェーンと同様に外側のスコープを探す
非同期処理
- Async Function内でawait式を使って処理を待っている間も、関数の外側では通常どおり処理が進む。つまり、awaitで待つのはAsync Function内のみ
const test = async () => { await new Promise((resolve) => { setTimeout(resolve, 10) )} } console.log("1番目に実行") test().then(() => { console.log("3番目に実行") }) console.log("2番目に実行")
- コールバック関数としてAsync Functionを渡す場合は意図しない挙動になる可能性があるため注意
- parse: JSON形式の文字列→JavaScriptオブジェクト
// JSONはダブルクォートのみを許容するため、シングルクォートでJSON文字列を記述 const json = '{"id": 1, "name": "bob"}' const obj = JSON.parse(json)
- stringify: JavaScriptオブジェクト→JSON形式の文字列
const obj = {id: 1, name: "bob"} const json = JSON.stringify(obj)
Date
- 標準は直感的でないためサードパーティライブラリを使う
参考文献
Djangoベストプラクティス
開発
設定ディレクトリ名はconfigとする
django-admin startproject config .
staticとtemplatesはプロジェクト直下に
project |--manage.py |--config/ |--app/ |--static/ |--templates/
viewは基本のクラスベースで書き、徐々にリファクタリングする
class LoginView(View): def get(self, request, *args, **kwargs): context = {"form": LoginForm()} return TemplateResponse(request, "accounts/login.html", context) login = LoginView.as_view()
viewには複雑なロジックは書かない
- fat viewは避けたいので、なるべく一つのテーブルの処理に関しては、modelに記述するのが良い
Userモデルの拡張
# models.py class CustomUser(AbstractUser): xx
# settings.py # appはアプリ名 AUTH_USER_MODEL = "app.CustomUser"
- get_user_model()でモデルを取得
# Userモデルの取得
get_user_model().objects.all()
コンテナで動かす時は0.0.0.0.を指定する
python manage.py runserver 0.0.0.0:8000
大量のオブジェクトを作る時はbulk_createを使う
user_objects = [] for _ in range(100000): user_objects.append(User(name="jobs")) # user_objects のデータをDBに一括登録する User.objects.bulk_create(user_objects)
本番
静的ファイル(リクエストに応じて中身を変更せずにそのまま返す)はWebサーバ(Nginxなどのリバースプロキシ)に置く
- アプリケーションサーバ(Django)は処理が必要なリクエストのみに集中することでパフォーマンスを上げる
- セキュリティの観点より静的ファイルの配信元(STATIC_ROOT)とアプリケーション側の格納場所(STATICFILES_DIRS)は分ける
# STATICFILES_DIRSからSTATIC_ROOTへ静的ファイルをコピー python manage.py collectstatic
メディアファイルも同様で、MEDIA_ROOTにS3などのディレクトリを指定し、Webサーバ側で処理する
settings.pyはbase.pyに共通部分をそれ以外に差分を書く
config/ |--settings/ |--base.py |--local.py |--production.py |--test.py
# local.py from .base import *
python manage.py runserver --settings config.settings.local.py # モデルの差分をチェックしてマイグレーションファイルを作成 python manage.py makemigrations --settings config.settings.local # マイグレーションファイルの内容をDBに反映してテーブルを作成・更新 python manage.py migrate
参考文献
RDBコンテナとDjangoコンテナのシークレット変数の設定
はじめに
RDB(Postgresなど)コンテナとDjangoコンテナのシークレット変数の設定を整理しました。
docker-composeで組むことが多いのですが、いつも迷うのでdocker-composeの場合で整理しました。
シークレット変数は環境変数又はファイルから読み込みますが、ファイルから読み込む形にします。
開発環境を想定します。
設定方法
.envにシークレット変数を記述する
# PostgreSQL初期化用(docker-compose.ymlで使用)
POSTGRES_USER=xxx
POSTGRES_PASSWORD=xxx
POSTGRES_DB=xxx
# Django設定用(settings.pyで使用)
DATABASE_URL=postgres://<user>:<password>@db:5432/<db>
SECRET_KEY=xxx
※<user>などは${POSTGRES_USER}のように展開不可のためハードコードするか、settings.pyでPOSTGRES_USERなどを読み込み、DATABASE_URLを構築する必要がある
RDBはコンテナ起動時にroot userやDBを作成する必要があるためdocker-compose.ymlから読み込む
services: db: environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB}
Djangoはsettings.pyから読み込む(docker-compose.ymlにenvironmentの記述不要)
import environ env = environ.Env() environ.Env.read_env(env_file=str(BASE_DIR) + "/.env") # シークレット変数の読み込み DATABASES = { # DATABASE_URLを読み込む "default": env.db(), } SECRET_KEY = env("SECRET_KEY")
React Todo List
アプリ開発時の気付き事項を記す。
アプリ開発
- JSXでは全ての要素は閉じる
<input type="text" />
- formに含まれるボタンが押されたとき、デフォルトでform全体が送信されるため、ページのリロードが発生する。そのため、追加されたToDoがすぐに消えてしまう。よってformタグは不要
- li 要素に key を追加することで、Reactがどのアイテムが追加、削除、または変更されたかを効率的に追跡でき、警告も消える
<ul> {todos.map((todo, index) => ( <li key={index}> <p>{todo}</p> </li> ))} </ul>
- Stateの更新の際のset~の更新対象となる変数が配列のときは新しい配列を渡して更新する
const [todos, setTodos] = useState([]); const newTodos = [...todos, text]; setTodos(newTodos);
- 三項演算子の中でも複数要素を返す場合は<>が必要
- JavaScriptではオブジェクト内で同じキーを複数回使用すると、最後に記述された値で以前の値が上書きされる
const todo = { text: updateText, completed: false, updated: false, updated: true, }
JavaScript カレンダーアプリなど
カレンダーアプリなど作成時の気付きを記す。
- エントリーポイントとは、アプリケーションの中で一番最初に呼び出される部分のこと
- Fetch APIはHTTP通信を行ってリソースを取得するためのAPI
- fetch
- Promiseを返す
- thenメソッドが使える(Promiseを返すから)
- jsonメソッドが使える
// HTTPリクエスト const userId = "任意のGitHubユーザーID"; fetch(`https://api.github.com/users/${encodeURIComponent(userId)}`);
// HTTPレスポンス const userId = "js-primer-example"; fetch(`https://api.github.com/users/${encodeURIComponent(userId)}`) .then(response => { console.log(response.status); // => 200 return response.json().then(userInfo => { // JSONパースされたオブジェクトが渡される console.log(userInfo); // => {...} }); });
// commanderモジュールからprogramオブジェクトをインポートする import { program } from "commander"; // コマンドライン引数をcommanderでパースする program.parse(process.argv); // ファイルパスをprogram.args配列から取り出す const filePath = program.args[0];
- Node.jsでファイルの読み書きを行うには、標準モジュールのfsモジュールを使う
- Node.jsの標準モジュールはnode:fsのようにnode:プリフィックスをつけてインポートできる。 プリフィックスを付けないfsでもインポートできるが、npmからインストールしたサードパーティ製のモジュールとの区別が明確になるため、付けておくことが推奨
// fs/promisesモジュール全体を読み込む
import * as fs from "node:fs/promises";
// fs/promisesモジュールからreadFile関数を読み込む
import { readFile } from "node:fs/promises";
- ファイルの読み書きは存在の有無や権限、ファイルシステムの違いなどによって例外が発生しやすいので、必ずエラーハンドリング処理を書く
- markedパッケージを使ってMarkdown文字列をHTML文字列に変換可能
- テスティングフレームワークのmochaパッケージをインストールし、npm testコマンドでmochaコマンドを実行することによりテストが可能
$ npm test > mocha test/
- event.preventDefaultメソッドは、submitイベントの発生元であるフォームが持つデフォルトの動作をキャンセルする
- フォームが持つデフォルトの動作とは、フォームの内容を指定したURLへ送信するという動作
- form要素に送信先が指定されていないため、現在のURLに対してフォームの内容を送信
- 現在のURLに対してフォームの送信が行われると、結果的にページがリロードされる
- リロードさせないためにevent.preventDefaultメソッドを使う
formElement.addEventListener("submit", (event) => { // submitイベントの本来の動作を止める event.preventDefault(); console.log(`入力欄の値: ${inputElement.value}`); });
- イベントが発生したことを元に処理を進める方法をイベント駆動(イベントドリブン)
- commanderでコマンドライン引数にオプションを付ける
// コマンドライン引数の取得とオプションの設定 import {program} from 'commander'; program.option('-m, --month <number>'); program.parse(process.argv); const options = program.opts(); console.log(options.month;);
- 異常終了(0は正常終了)
process.exit(1);
- 改行せずに表示
process.stdout.write('xxx');
- 数字の二桁表示
// iは数字 i = i.toString().padStart(2, '0');
- 複数の空白の作り方
// 空白数を返す let fn_blank = (num) => { return ' '.repeat(num); }
- Webブラウザでモジュールを使用するためには、scriptタグにtype="module"属性を追加
<body> <h1>ToDo List</h1> <script type="module" src="./index.js"></script> </body>
- プライベートフィールドは#を付ける
// TodoListModelはAppクラスの外からは触る必要がないためプライベートフィールドとする
export class App {
#todoListModel = new TodoListModel();
}
- confirmで削除前のメッセージを表示させる
// OKならtrue、キャンセルならfalseを返す let result = confirm('削除OK?')
参考文献