🐈

Next.js x TypeScript初心者向け利用ガイド(フロントエンド編)-Page 1/3

に公開

1. Next.js x TypeScript 初心者向け利用ガイド(フロントエンド編)

ここでは「Next.js」という Web フレームワークを利用して、Node.js、TypeScript の書き方とか、React フックを含めてフレームワークの利用方法、特にフロントエンドの画面周りを覚えてしまうって内容です。

文字数が多いので 3 ページに分割してご紹介します。

※6/11 一部文章・画像修正
※6/12 .env ファイルの使い方を修正
※6/15 Next.js のレンダリングを SSG と書いてたので SSR に修正

(目次)

(Page 1 / 3)

  1. Next.js×TypeScript 初心者向け完全ガイド(フロントエンド編)
    1-1. はじめに
    1-2. JavaScript の歴史を軽くおさらい
    1-3. TypeScript のプログラムを動かす実行環境の準備
    1-4. Visual Studio Code のインストールと準備

  2. TypeScript の仕組みと Next.js プロジェクトの作成
    2-1. TypeScript 単体での実行方法について
    2-2. ビルドツールについて補足
    2-3. Next.js プロジェクトの作成
    2-3-1. Next.js プロジェクト作成の手順
    2-4. Visual Studio Code でプロジェクトのプログラムを実行する
    2-4-1. Visual Studio Code でプロジェクトのプログラムを実行する手順
    2-5. package.json について
    2-6. 1 プロジェクトに frontend、backend プロジェクトを配置したマルチプロジェクトを利用する場合
    2-7. Prettier、ES Lint について
    2-7-1. Prettier
    2-7-2. ES Lint

  3. TypeScript の基本について
    3-1. TypeScript の命名規則
    3-2. tsx ファイルについて
    3-3. TypeScript の基本構文
    3-3-1. 基本のデータ型、関数、配列、オブジェクト型、Record 型の利用方法
    3-3-2. オブジェクトの参照コピーと値コピーについて
    3-3-3. テンプレートリテラル
    3-3-4. スプレッド構文
    3-3-5. 配列の分割代入
    3-3-6. オブジェクト型の分割代入
    3-3-7. オブジェクトの省略記法
    3-3-8. オプショナルプロパティ、オプショナルチェインニング
    3-3-9. 条件分岐 (if ~ else)
    3-3-10. 三項演算子
    3-3-11. 等価・不等価演算子 (===, !==)
    3-3-12. ループ (for 文)
    3-3-13. 利用機会の多い標準関数 (forEach 関数、map 関数、find 関数、filter 関数、reduce 関数)
    3-4. import と export について
    3-5. TypeScript データ型の一覧
    3-6. ユーザー定義型の管理方法

  4. Web アプリの概念説明
    4-1. Web アプリ、HTTP リクエスト / レスポンスの概念について
    4-2. Next.js アプリのレンダリング方式
    4-3. frontend / backend の API で利用する HTTP メソッドの種類について
    4-4. JSON データについて
    4-5. Node.js その他のフレームワーク

  5. Next.js 概要、Next.js で利用する React フック
    5-1. Next.js が持っている機能
    5-2. Next.js の基本
    5-2-1. Next.js を実行した時の動き
    5-2-2. .env、next.config.ts の設定方法
    5-3. コンポーネントと Props の概要
    5-4. スタイルとイベントの使い方
    5-5. コンポーネントと Props で子コンポーネントへの値の渡し方
    5-6. React フックの概要
    5-7. state 管理 (useState)
    5-8. 値の保持 (useRef)
    5-9. 副作用 (useEffect)
    5-10. 再レンダリングが発生する条件
    5-11. メモ化

(Page 2 / 3)

  1. Next.js 概要、Next.js で利用する React フック (続き)
    5-12. コンポーネントのメモ化 (memo)
    5-13. 関数のメモ化 (useCallback)
    5-14. カスタムフックの概要
    5-15. カスタムフックで開閉式のサイドバーの状態管理 (useSettingSidebar + localforage)
    5-16. カスタムフックで HTTP リクエストの状態管理・例外処理 (useApiRequest)をして、純粋な関数 (createUserService + Axios) で HTTP リクエストを実行

  2. 共通ユーティリティ関数、共通コンポーネント
    6-1. AJV Validation でのデータ検証 (ajvValidate)
    6-2. DOMPurify で文字列の無害化 (sanitize)
    6-3. MUI 用モーダル内の Box のスタイル付きコンポーネント (ModalContainer)
    6-4. MUI 用エラーメッセージ表示用 Snackbar 内のスタイル付きコンポーネント (SnackbarContainer)

  3. ATOMIC デザインについて
    7-1. ATOMIC デザインの説明
    7-2. ATOMIC デザインのフォルダ構成案
    7-3. ATOMIC デザインでの React フック利用イメージ

  4. パーツごとのサンプル画面の作成
    8-1. Web テーブル (AG-Grid + Axios)
    8-2. モーダル (MUI)
    8-3. モーダル登録フォーム (MUI + React Hook Form + AJV + Axios)

(Page 3 / 3)

  1. サンプルアプリの作成
    9-1. 前項のサンプルパーツを 1 画面にまとめたページの作成
    9-2. サンプルアプリの公開 URL (GitHub、StackBlitz で公開)

  2. あとがき

1-1. はじめに

この Next.js x TypeScript 初心者向け利用ガイド(フロントエンド編)はなるべく初心者向けに書きました。上級者向けではないのでご了承ください。中級者の方には役に立つ内容はあるかも?

このページで初心者の方に Web アプリ作りの楽しさを共有できるといいなと思って書きました。

Next.js が初心者向けかどうかはありますが、TypeScript でプログラムを実行するための実行環境の用意や、開発環境の構築がとても速いと思いますので、まずはこれで慣れてもらおうって感じです

また、Next.js はある程度最初から必要なものが揃っていますので、React 単独で利用するよりは入りやすいかもしれません。

この記事を書いた人は、最初は React や、Next.js の本を読んでもあまり理解できませんでした。
本ではある程度のことは書いてますが知りたいことが不足していたり、覚えるところ、覚えなくてもいいところなど判断が難しいところもあります。

また、文章だけ読んでも、実際に手を動かしてみないとわからないこともあります。
プログラミングを覚える時は、何かを作るために実際に書いてみる、イメージが沸く、機能を実際に使ってみる、わからないことをすぐに調べることが、もっとも近道かもしれません。

また、このページで利用している HTML、CSS のスタイルの最小限の知識は必要です。
HTML、スタイルの説明はこのページのプログラムコード中では作成済みのものを利用していて、詳細の説明は省略しています。

初版なので誤記等があるかもしれないのと、言葉足らずで文章が変なところもあるかもしれませんが、興味を持った方は下記以降お付き合いください。


1-2. JavaScript の歴史を軽くおさらい

昔の JavaScript はブラウザ上だけで動く言語でしたが、2009 年頃に「Node.js」という実行環境が登場し、サーバー上でも JavaScript が使えるようになりました。

(ブラウザで動く JavaScript と Node.js で動かす JavaScript の違い)
ブラウザで動くJavaScriptとNode.jsで動かすJavaScriptの違い

簡単にいうと、JavaScript でもファイル操作やデータベース操作ができるようになったようなイメージです。

他の言語なら普通にできるのですが、JavaScript には元々ありませんでした。
他の言語を使えばと思われるかもしれませんが、JavaScript はスクリプト型で書きやすい言語であること、今は画面側(フロントエンド)に動きのあるものが求められ JavaScript が必須になってきています。
また、サーバー側(バックエンド)も同じ言語で作れるなどメリットが多いこともあって、いま人気があるプログラミング言語の 1 つなのかなと思います。

ただし、JavaScript はデータ型(データの種類)の扱いがゆるいので、もっと安全に開発したいときは「TypeScript」が便利です。
TypeScript は JavaScript の上位互換の言語でもあり、TypeScript で JavaScript コードを書いても動きます。

今では TypeScript と React、Next.js などのフレームワークと組み合わせて、Web システム開発にもよく使われるようになりました。

以降では初心者向けに「Next.js」というフレームワークを利用して、Node.js、TypeScript、React でのアプリ作成のイメージをつかんでもらうような内容としています。


1-3. TypeScript のプログラムを動かす実行環境の準備

TypeScript を動かすためには Node.js のインストールが必要です。
しかし、Windows の実行するパソコンでは Node.js のバージョンを切り替えられるようにしておくとよいです。

NVM for windows をパソコンにインストールすることで、Node.js をバージョン切替しながら利用することができます。

(NVM for windows の実行イメージ)
NVM for windows の実行イメージ

下記の GitHub サイトの NVM for Windows のページからダウンロードし、インストールして下さい。

(NVM for Windows ダウンロードページ)
https://github.com/coreybutler/nvm-windows/releases
「nvm-setup.zip」を選択

(NVM for Windows)
NVM for Windows

NVM は開発専用なので、本番環境では利用しません。自分の Windows PC のみにインストールをしましょう。

※Docker とかで仮想の実行環境を作ることも可能ですが、ここで扱いません。

Power Shell を実行し、下記のコマンドを順番に実行して Node.js のインストールをします。

(PowerShell)

nvm install
nvm use 22
# nvm use では確認ダイアログが開かれる場合がありますが、OK を押して進めてください

# nvm current で現在利用中のバージョンを確認可

(NVM コマンド一覧)

コマンド 説明
nvm install 22 Node.js の指定バージョンをインストール。今回は 22 を利用。
nvm use 22 Node.js の指定バージョンを利用。今回は 22 を利用。
nvm current 現在利用中の Node.js のバージョン確認。
nvm list インストール済みの Node.js バージョン一覧。

(PowerShell NVM コマンド実行画面)
PowerShell NVM コマンド実行画面


1-4. Visual Studio Code のインストールと準備

・ TypeScript を実行するためには開発ツールを利用するため、インストールが必要です。
下記の Visual Studio Code ページからダウンロードし、インストールをして下さい。
インストール画面は特に選択するものもないので普通にインストールで OK です。

https://code.visualstudio.com/download
「User Installer x64」を選択

(Visual Studio Code ダウンロードページ)
Visual Studio Code ダウンロードページ

・続いて Visual Studio Code 拡張機能のインストールを行います。
左メニューバーの「拡張機能」アイコンをクリックし、検索に拡張機能名を入力すると候補が表示されるので拡張機能を選択し、インストールしてください。

(Visual Studio Code 拡張機能にインストールするもの)
Visual Studio Code 拡張機能にインストールするもの

(インストールする拡張機能の一覧)

拡張機能名 説明
Auto Rename Tag 開始タグを修正すると終了タグも自動修正
ESLint 静的コード解析ツール(構文チェック)
Git Graph Git のコミット履歴をグラフ化
GitLens — Git supercharged チーム開発の担当者の可視化
indent-rainbow インデントに色を付けてくれる
IntelliCode コード補完拡張機能
IntelliCode API Usage Examples ※上に付帯?
Japanese Language Pack for Visual Studio Code VS Code 日本語化
Prettier - Code formatter コード整形

(Visual Studio Code 拡張機能のインストール後の画面)
Visual Studio Code 拡張機能のインストール後の画面

※Markdown Preview Enhanced は無視してください。


2. TypeScript の仕組みと Next.js プロジェクトの作成

ここでは TypeScript がどうやって動くのかを説明したいと思います。

2-1. TypeScript 単体での実行方法について

TypeScript を単体で実行することはありませんが、ここでは TypeScript コードを単体で実行してどうして動くのかを解説したいと思います。

TypeScript ファイルを単体で実行する手順

・デスクトップの任意のフォルダなどに index.ts ファイルを作成し、下記のコードを記載します。

TypeScript:C:\Users\user\Desktop\test\index.ts

index.ts
const message: string = "mew";
console.log(message);

・Windows の PC で TypeScript の実行検証をするために PowerShell で TypeScript をグローバルにインストールします。
※実際はプロジェクトフォルダごとにインストールしますので、検証後はアンインストールします。

PowerShell

#セキュリティポリシーを一時的に緩める
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process

#TypeScriptをグローバルにインストール
npm install typescript -g

・TypeScript ファイルを TypeScript の tsc コマンドで ts ファイルを js ファイルに変換します。

PowerShell

tsc C:\Users\user\Desktop\test\index.ts

(tsc コマンドで ts ファイルを js ファイルに変換)
PowerShell tsc コマンド実行後

・Node.js の node コマンドで実行すると、コンソールに"mew"の表示が確認できます。

PowerShell

#Node.js の node コマンドで実行
node C:\Users\user\Desktop\test\index.js

(PowerShell TypeScript 単体実行)
PowerShell NVM コマンド実行画面

・最後に、TypeScript はグローバルからアンインストールしてください。

PowerShell

#グローバルの TypeScript をアンインストール
npm uninstall typescript -g

上記は面倒な手順でしたが、実際の Web アプリは 1 ファイルで動いていはないですが、実際はこの手順を実行せずに、モジュールやビルドツールで自動化して利用することになります。

2-2. ビルドツールについて補足

Node.js の何かのフレームワークでプロジェクトを作成した時にルートフォルダ内に package.json が自動作成され、このファイルにビルドツールを実行するためのスクリプトを記載して、実行することができます。

ここではビルドツールの簡単な役割を説明します。プロジェクトの作成方法については後項に記載します。

ビルドツールでは、開発実行でプログラムの動きを確認しながら動かす方法(dev)と、本番環境用にプログラムファイルをビルド(build)、ビルドしたものを実行する(start)という流れです。

実行方法としては、npm run xxx の形式で実行をすることができます。

(例) 開発実行

PowerShell

npm run dev

・dev については Visual Studio Code のデバッグ機能と連動させることができます。(後項に記載)

また、ビルドツール自体はいろいろなものがあります。

(ビルドツール役割)

項目 package.json の Script Script に記載のコマンド例(Next.js の場合のデフォルト)
開発実行 dev next dev --turbopack
ビルド(本番用ファイル作成) build next build
本番用実行 start next start
Lint でコードチェック lint next lint

(ビルドツール一覧)

ビルドツール 説明
Turbopack Webpack の後継。Next.js では next dev に Turbopack を利用できる。next build、next start については 内部的に Webpack を利用。(順次切り替わり予定)
Webpack 最もよく使われているビルドツール。開発自体は停止している。
Vite React や Vue.js などの frontend に利用できる。backend は利用できない。
その他 esbuild、他...

とはいえ、自前でビルドツールの設定を用意するのはとても大変な作業です。
Next.js などのフレームワークを利用することで、このプロジェクトの作成コマンドで、TypeScript の実行からビルドツールの利用まで自動で構築を行ってくれます。


2-3. Next.js プロジェクトの作成

Next.js のフレームワークからプロジェクトを作成する方法が、TypeScript を実行する環境を作る方法としてとても速く構築できるので、ここで説明したいと思います。

TypeScript の実行&ビルドツールの利用設定までも自動を行ってくれますので悩むことも少なそうです。

また、Next.js フロントエンド、バックエンドの両方を作成することができます。

2-3-1. Next.js プロジェクト作成の手順

・最初に Next.js のコマンドでプロジェクトフォルダ・ファイルを自動作成します。ビルドツール設定も自動で構築されます。まずは Next.js のコマンドで新しいプロジェクトを発行したいと思います。下記の 3 つのコマンドを 1 行ごとに順番に実行していきます。

※以下の設定が難しい場合は、すぐ動くものを GitHub 上に用意していますのでそちらを利用ください。(後項に記載)

PowerShell

#セキュリティポリシーを一時的に緩める
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process

#cdで任意の場所に移動し、npxコマンドで、Next.jsのfrontendプロジェクトを作成
cd C:\Users\user\Desktop
npx create-next-app@latest

・次に対話形式で質問を聞かれますので、下記のように設定します。

PowerShell

#対話形式で選択する
#今回はTailwind CSSの利用なし、import aliasのカスタマイズはなしとしてます
Need to install the following packages:
create-next-app@15.3.1
Ok to proceed? (y) y

√ What is your project named? ... frontend
√ Would you like to use TypeScript? ... No / 〇Yes
√ Would you like to use ESLint? ... No / 〇Yes
√ Would you like to use Tailwind CSS? ... 〇No / Yes
√ Would you like your code inside a `src/` directory? ... No / 〇Yes
√ Would you like to use App Router? (recommended) ... No / 〇Yes
√ Would you like to use Turbopack for `next dev`? ... No / 〇Yes
√ Would you like to customize the import alias (`@/*` by default)? ... 〇No / Yes..
#... 中略 ...
Success! Created frontend at C:\Users\user\Desktop\frontend

上記で実行した結果、最後に緑で Success が表示されれば OK です。

(npx create-next-app@latest の実行結果)
npx create-next-app@latestの実行結果
npx create-next-app@latestの実行結果

(作成されたプロジェクト内容の説明)

プロジェクト構成

frontend ... プロジェクトルートフォルダ
  - .next ... ビルド結果等
  - node_modules ... npmパッケージのインストール先(自動)
  - public ... 画像、ファイルなどをそのまま公開する場所
  - src ... プログラムを書く場所
    - app ... Next.jsのApp routerを利用するのでこのフォルダ下のフォルダにpage.tsxが入っているフォルダが画面パスになる
      - favicon.ico ... お気に入りアイコン
      - global.css ... 共通CSS
      - layout.tsx ... 共通レイアウト
      - page.module.css ... このフォルダのpageのCSS
      - page.tsx ... このフォルダのpage(tsxファイル: HTMLとTypeScriptが混じっているもの)
  - .gitignore ... GitHubでソース管理する際の除外設定
  - eslint.config.mjs ... ESLint設定ファイル
  - package.json ... プロジェクト管理ファイル(npmスクリプト、ライブラリ等)
  - package-lock.json ... package.jsonのロックファイル ※今のところ詳細は考えない
  - tsconfig.json ... TypeScript設定ファイル
  - README.md ... プロジェクトの説明等のテキスト(マークダウン)
  - next.config.ts ... Next.js設定ファイル
  - next-env.d.ts ... Next.js関連の型ファイル

2-4. Visual Studio Code でプロジェクトのプログラムを実行する

2-3.項で作成したプロジェクトを Visual Studio Code で開いて実行できるようにしましょう。

2-4-1. Visual Studio Code でプロジェクトのプログラムを実行する手順

・左サイドメニューの「エクスプローラー」、または上部メニューの「ファイル」から先ほど作成したプロジェクトのフォルダを開いてください。途中で「このフォルダー内のファイルの作成者を信頼しますか?」と聞かれた場合は「はい、作成者を信頼します」を選択してください。

左のエクスプローラーにプロジェクトフォルダと内容が表示されれば OK です。

(Visual Studio Code でプロジェクトフォルダを開く画面)
Visual Studio Codeでプロジェクトフォルダを開く画面1
Visual Studio Codeでプロジェクトフォルダを開く画面2
Visual Studio Codeでプロジェクトフォルダを開く画面3

・このままでは、Visual Studio Code のターミナルからプログラムを実行できないので、プロジェクトフォルダのみセキュリティポリシーの有効化をする必要があります。

プロジェクトフォルダ直下に「.vscode」フォルダを作成し、「settings.json」ファイルを作成し、以下の内容を入力します。
ここで必要なのは"プロジェクト内でターミナルのセキュリティポリシーの有効化"と、"prettier でファイル保存時にコードの自動フォーマット"の設定です。

設定後は Visual Studio Code を再起動(閉じて再実行)してください。

json:.vscode/settings.json

.vscode/settings.json
{
  // VS Code Terminal: プロジェクト内でターミナルのセキュリティポリシーの有効化
  "terminal.integrated.env.windows": {
    "PSExecutionPolicyPreference": "RemoteSigned"
  },

  // Editor: プロジェクト内で保存時の自動フォーマット設定
  "editor.defaultFormatter": "esbenp.prettier-vscode",

  // Editor: 保存時に実行
  "editor.formatOnSave": true
}

(Visual Studio Code の settings.json)
Visual Studio Codeのsettings.json

・上部メニューの「ターミナル」を実行し、下に表示された「ターミナル」にコマンドを入力してください。

PowerShell

# ルートプロジェクトを分けている場合
# cd frontend

npm run dev

(Visual Studio Code のターミナルから npm run dev で実行)
Visual Studio Code のターミナルからnpm run devで実行

・ブラウザを開き、「localhost:3000」を入力すると、画面に Next.js が表示されていることが確認できます。

(ブラウザで Next.js の表示画面)
ブラウザでNext.jsの表示画面

ただし毎回コマンドで実行するのは面倒なことと、Visual Studio Code でコードのデバッグ停止をするため、デバッグ実行ボタンから簡単に実行できるようにします。
プロジェクトフォルダ直下の「.vscode」フォルダに「launch.json」ファイルを作成し、以下の内容を入力します。(Next.js 公式ドキュメントの手順と同様です。)

json:.vscode/launch.json

.vscode/launch.json
{
  // IntelliSense を使用して利用可能な属性を学べます。
  // 既存の属性の説明をホバーして表示します。
  // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Next.js: debug client-side",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "args": ["dev", "-p", "3000"],
      "runtimeArgs": ["--inspect", "./node_modules/next/dist/bin/next"],
      "skipFiles": ["<node_internals>/**"],
      "serverReadyAction": {
        "action": "debugWithChrome",
        "killOnServerStop": true,
        "pattern": "- Local:.+(https?://.+)",
        "uriFormat": "%s",
        "webRoot": "${workspaceFolder}"
      }
    }
  ]
}

(Visual Studio Code の launch.json)
Visual Studio Code のlaunch.json

・緑のボタンに「Next.js: debug client-side」が表示されますので、ボタンをクリックするとブラウザが自動で起動し、同様に画面に Next.js が表示されることが確認できます。

(Visual Studio Code の実行ボタンからデバッグ実行)
Visual Studio Code の実行ボタンからデバッグ実行

(ブラウザで Next.js の表示画面)
ブラウザでNext.jsの表示画面

・また、プログラムコードにブレークポイントをつけると、その位置でプログラムを停止することができます。

(Visual Studio Code のデバッグ停止)
Visual Studio Code のデバッグ停止


2-5. package.json について

Node.js では npm というパッケージマネージャーを使用して、プロジェクトフォルダ直下にある package.json に Script を記載してプログラムを実行したり、必要なパッケージ(ライブラリ)を管理しています。

プロジェクトのフォルダで "npm install xxx" をすると "node_module"フォルダにパッケージがダウンロードされて、"package.json"の"dependencies"に追加されます。これでライブラリをそのプロジェクト内で利用できるようになります。

"npm install xxx --save-dev" のように "--save-dev" を追加すると "devDependencies" に追加します。

"devDependencies" と "dependencies"の違いは、開発時のみ利用するものか、実行時のみ利用するかの違いになります。

Next.js の標準の package.json は以下のようになっています。

(Next.js の標準の package.json)

package.json
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "15.3.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.3.1",
    "typescript": "^5"
  }
}

追加するパッケージは様々なものがありますが、たくさん使いすぎるとよくわからくなってしまいがちですので、慎重に検討していただいたほうがよいです。

とはいっても何をダウンロードして良いかわからないと思います。
そこで frontend でよく使いそうなパッケージを npm trends で人気度を調査したり、実際に利用してみた感じで以下のものがおすすめだったので記載させていただきます。
以下のパッケージでアプリの frontend 部のことは大体できるイメージです。

下記のパッケージは現時点での最新ものです。npm install から行うと最新バージョンが利用できますので、最新のものをご利用ください。

(package.json よく使いそうなパッケージ)

package.json
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@emotion/react": "^11.14.0",
    "@emotion/styled": "^11.14.0",
    "@hookform/resolvers": "^5.0.1",
    "@mui/icons-material": "^7.1.0",
    "@mui/material": "^7.1.0",
    "@mui/x-date-pickers": "^8.4.0",
    "ag-grid-community": "^33.3.1",
    "ag-grid-react": "^33.3.1",
    "ajv": "^8.17.1",
    "ajv-errors": "^3.0.0",
    "ajv-formats": "^3.0.1",
    "axios": "^1.9.0",
    "chart.js": "^4.4.9",
    "class-transformer": "^0.5.1",
    "dompurify": "^3.2.6",
    "localforage": "^1.10.0",
    "luxon": "^3.6.1",
    "next": "15.3.0",
    "next-auth": "^5.0.0-beta.28",
    "react": "^19.1.0",
    "react-dom": "^19.1.0",
    "react-hook-form": "^7.56.4"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3.3.1",
    "@types/luxon": "^3.6.2",
    "@types/node": "^22.15.23",
    "@types/react": "^19.1.6",
    "@types/react-dom": "^19.1.5",
    "autoprefixer": "10.4.15",
    "eslint": "^9.27.0",
    "eslint-config-next": "^15.3.2",
    "typescript": "^5.8.3"
  }
}

(パッケージ一覧)

項目 パッケージ名 説明 備考
MUI @mui/material @emotion/react @emotion/styled React 専用の画面デザイン用の CSS フレームワーク
MUI icon @mui/icons-material MUI のアイコン
MUI Datepicker @mui/x-date-pickers MUI の Datepicker
React Hook Form react-hook-form @hookform/resolvers React 専用のフォームの状態管理
AJV ajv ajv-errors ajv-formats バリデーション (入力チェック)
AG Grid ag-grid-community ag-grid-react Web テーブル、無料版でも高機能、表の直接編集も可能
Axios axios HTTP リクエストをする (HTTP クライアント)
Chart.js chart.js グラフ表示
Class transformer class-transformer データ型を変換したい時
DOMPurify dompurify サニタイズ (文字列の無害化)
Localforage localforage ブラウザの IndexedDB にデータを一時保存したい時
Luxon luxon 日時操作
Next.js next react react-dom Next.js で利用
Auth.js next-auth@beta Next.js 専用の認証機能
devDependencies: ESLint @eslint/eslintrc eslint eslint-config-next ESLint で利用
devDependencies: Luxon @types/luxon Luxon の型
devDependencies: Node.js @types/node Node.js の型
devDependencies: Next.js @types/react @types/react-dom React の型
devDependencies: Autoprefixer autoprefixer ベンダープレフィックス付与
devDependencies: Prettier prettier コードフォーマット
devDependencies: TypeScript typescript TypeScript で利用

2-6. 1 プロジェクトに frontend、backend プロジェクトを配置したマルチプロジェクトを利用する場合

2-3 項では、Next.js を frontend プロジェクトとして用意しましたが、Web アプリは実際は backend アプリを組み合わせて動作しています。

自習用では frontend だけでなく、backend も同時に操作できたほうが良いと思います。

ここではルートフォルダを next-root-app として作成し、その中に frontend、backend プロジェクトを配置したマルチプロジェクトを GitHub で公開していますので、慣れていないかたはこちらを利用していただいてもよいかもしれません。

(GitHub)
・next-root-app
https://github.com/mofuweb1/next-root-app

ダウンロードしていただき、Visual Studio Code で下記のコードを順番に実行し、frontend、backend それぞれに移動して npm install すると実行することができます。

(Visutal Studio Code のターミナルでコマンドを順に実行して、インストールをする)

cd frontend
npm install

cd ..
cd backendend
npm install

(プロジェクト構成)

next-root-app ... 2つのプロジェクトを入れるフォルダ(フォルダをプロジェクト名としているのみ)
  - .vscode
     - launch.json
     - settings.json

  - backend ... Next.jsアプリ(アプリ画面なし、APIで利用、DB・ファイル操作等)
    - next.jsの内容

  - frontend ... Next.jsアプリ(アプリ画面あり)
    - next.jsの内容

  - .gitattributes
  - LICENSE
  - README.md

Visual Studio Code のデバッグ実行ボタンで frontend、backend を同時起動できるようになっています。frontend のみブラウザでも開きます。.vscode/launch.json の設定は以下となっています。
backend プロジェクトについては準備中のため、ブラウザで localhost:5000/api/users のみアクセス可能となっています。こちらについては backend 編の記事を書くときに準備します。

マルチプロジェクト時の launch.json の設定を記載します。この設定で、frontend / backend を"Next.js: debug compounds"から同時実行することができます。

.vscode/launch.json

.vscode/launch.json
{
  // IntelliSense を使用して利用可能な属性を学べます。
  // 既存の属性の説明をホバーして表示します。
  // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Next.js: debug server-side",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}/backend",
      "args": ["dev", "-p", "5000"],
      "runtimeArgs": ["--inspect", "./node_modules/next/dist/bin/next"],
      "skipFiles": ["<node_internals>/**"]
    },
    {
      "name": "Next.js: debug client-side",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}/frontend",
      "args": ["dev", "-p", "3000"],
      "runtimeArgs": ["--inspect", "./node_modules/next/dist/bin/next"],
      "skipFiles": ["<node_internals>/**"],
      "serverReadyAction": {
        "action": "debugWithChrome",
        "killOnServerStop": true,
        "pattern": "- Local:.+(https?://.+)",
        "uriFormat": "%s",
        "webRoot": "${workspaceFolder}/frontend"
      }
    }
  ],
  "compounds": [
    {
      "name": "Next.js: debug compounds",
      "configurations": ["Next.js: debug server-side", "Next.js: debug client-side"]
    }
  ]
}

デバッグ実行したときの画面になります。フロントエンドは Next.js の画面が表示されます。

フロントエンドのデフォルトの画面では、実際に構築するアプリをどう作ればよいかイメージが難しいので、共通のレイアウトを header / main (sidebar / content) / footer の構成にタグ、スタイルを修正しました。
※サンプルコードについてモバイル表示は未対応です。

backend は準備中のため、localhost:5000/api/User のみアクセス可能。json のダミーデータを返却するようになっています。実際は認証などが必要になってきます。

(next-root-app プロジェクトと実行画面)
next-root-app プロジェクト一覧
next-root-app デバッグ実行
next-root-app フロントエンド表示
next-root-app バックエンドエンド表示


2-7. Prettier、ES Lint について

Visual Studio Code で TypeScript を書くときに、ESLint と Prettier はほぼ必須なので覚えておきましょう。
この 2 つを利用することで、ファイル編集または保存時のタイミングでコードチェックをすることと、コードの自動フォーマットを行ってくれます。
基本的には Visual Studio Code 拡張機能で利用できます。

2-7-1. Prettier

Prettier ではファイル保存時にコードの自動フォーマットを行ってくれます。
Visual Studio Code で実行するためには拡張機能のインストールと、prettier 関連のパッケージのインストール、".prettierrc"、".vscode/settings.json" の設定が必要になります。

Prettier の一般的な設定の例を記載します。

.prettierrc

.prettierrc
{
  "printWidth": 200,
  "semi": true,
  "trailingComma": "none",
  "bracketSameLine": true,
  "proseWrap": "never"
}

下記の部分は Prettier デフォルトの設定だと整形されたコードが読みずらくなるため、このようにしています。

(例)"printWidth": 200、"trailingComma": "none"、"bracketSameLine": true,
※1 行を長めに 200、末尾のカンマは削除、閉じタグは同じ行にしています。

2-7-2. ES Lint

ESLint ではファイル編集時にコードのチェックを行ったり、npm run lint でプロジェクト全体に対してチェックを行うことができます。
Visual Studio Code で実行するためには拡張機能のインストールと、lint 関連のパッケージのインストール、".prettierrc"、".vscode/settings.json" の設定が必要になります。

ESLint の設定ファイルは拡張子がいろいろありますが、ここでは eslint.config.mjs を利用して設定します。
設定内容については複雑にしすぎるとよくわからなくなるので、できるだけシンプルな例として記載します。

このページの方針としては、データ型を厳しくしすぎないようにしています。

javascript:eslint.config.mjs

eslint.config.mjs
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
  baseDirectory: __dirname,
});

const eslintConfig = [
  ...compat.extends("next/core-web-vitals", "next/typescript"),

  // 追加ルール
  {
    files: ["**/*.{ts,tsx}"],
    rules: {
      // 戻り値の型が明示されていない関数に警告を出す (frontend、backend共用)
      "@typescript-eslint/explicit-function-return-type": "warn",

      // console は 本番時は禁止、開発時のみ
      // "no-console": "error",
      "no-console": "off",

      // 型に関することは関数内に宣言禁止
      "no-restricted-syntax": [
        "error",
        {
          selector: "FunctionDeclaration TSInterfaceDeclaration",
          message: "関数内でinterfaceを定義しないでください",
        },
        {
          selector: "FunctionDeclaration TSTypeAliasDeclaration",
          message: "関数内でtypeを定義しないでください",
        },
        {
          selector: "FunctionDeclaration ClassDeclaration",
          message: "関数内でclassを定義しないでください",
        },
      ],

      // 1ファイル、1関数あたりの行数制限(読みにくいので)
      // お好みで
      // "max-lines": ["warn", 500],
      // "max-lines-per-function": ["warn", 500],

      // ネスト制限 (読みにくいので)
      // お好みで
      // "max-depth": ["warn", 3],
    },
  },
];

export default eslintConfig;

ESLint については、Next.js プロジェクト作成時にスクリプトが自動設定されているので、ファイル全体に対しても実行することができます。

Visual Studio Code - Terminal (PowerShell)

npm run lint

3. TypeScript の基本について

ここでは TypeScirpt の基本について説明したいと思います。

TypeScript の元である JavaScript のバージョンは大きく分けて ES5、ES6(ES2015)のバージョンを境目に新しい書き方ができるようになっていますので、ES6 以上の書き方で説明します。

TypeScript のことを調べる場合は JavaScript / TypeScript ドキュメント の両方を確認する必要があります。構文の基本的なことの確認は JavaScript ドキュメント、TypeScirpt 独自のこと調べる時は TypeScirpt ドキュメントを確認する感じです。

英語で読めない場合は翻訳すればとりあえず読めると思います。ただ、ドキュメントページの意図がよくわからない、理解できないことがあるとは思います。全部を理解する必要はないです。

(ドキュメント)

ドキュメント 説明
JavaScript ドキュメント https://developer.mozilla.org/ja/docs/Web/JavaScript
TypeScript ドキュメント https://www.typescriptlang.org/ja/

プログラムのわからないことは人に聞くのが一番良いです。またはいまは ChatGTP に聞いたりして、ある程度答えを教えてもらうこともできます。
注意事項として、ChatGTP は必ず正解を教えてくれるとは限らないのでそれが正しいかは確かめる必要があります。

3-1. TypeScript の命名規則

TypeScript の命名規則について表に記載します。
一般的な TypeScript の命名規則に対して、Next.js の独自ルールがありましたので表に整理しました。

コード内については ES Lint を設定していれば、ある程度警告を出してくれるので気づくことができるので、徐々に覚えてもらえればよいかと思います。

(frontend の命名規則の一例)

項目 命名規則
Next.js 内 のコンポーネントのフォルダ名 スネークケース(単数形 or 複数形) ui-sample など
Next.js 内 のコンポーネントのファイル名 スネークケース page.tsx、template.tsx、modal-form.tsx など ※Next.js コンポーネントのファイル名のみスネークケースにする
共通コンポーネントのファイル名 パスカルケース Header、SidebarModal、Container、SnackbarContentContainer
-
コンポーネントの関数名 パスカルケース Page、Template、ModalForm など
コンポーネント内のイベント関数名 on + パスカルケース onModalOpen など
Props で渡すイベント関数名 onSubmit={ handle + パスカルケース } onFormSubmit={handleSubmit} など
コンポーネント引数 (Props) 内のイベントのプロパティ名 on + パスカルケース onSubmit など ※handle ... を on ... にセット
-
カスタムフックのファイル名・関数名 use + パスカルケース useApiRequest など
カスタムフック以外の関数専用 ts ファイルのファイル名・関数名 キャメルケース ajvValidate、sanitize など
-
ローカル変数名 キャメルケース name、isDeleted など
ローカル関数名 キャメルケース doSomething など
-
クラス名 パスカルケース User など
インターフェース名 I + パスカルケース IUserService など
プロパティ名 キャメルケース emailAddress など
-
(backend) Next.js 内の route.ts 内の関数名 (固定) GET、POST、PATCH、DELETE など
(backend) DB 等 必要であれば t* / m* + スネークケース(複数形) m_users

3-2. tsx ファイルについて

Next.js は内部で React を利用しています。Next.js の TypeScript には、.tsx ファイルと.ts ファイルという 2 種類のファイルがあります。.tsx ファイルは画面用(または画面を表示するためのコンポーネント)のファイルとなり、.ts ファイルは画面以外のものを書くためのファイルになります。

.tsx ファイルの書き方が特殊ですが、TypeScript コード内に HTML を書いているイメージです。書き方のルールはありますが、下記のサンプルコードのようにプログラムの最後で html を return をすることで画面に表示しています。

最初に任意のフォルダを作成し、page.tsx を以下のコードで作成する必要があります。そうすることで、そのフォルダ名がそのページの URL として利用することできます。(Next.js の App router を利用)

以下のコードは画面のボタンから実行できるようにしているサンプルです。ボタンを実行するためには先頭に use client をつける必要があります。

TypeScript:{任意のフォルダ}/page.tsx

/{任意のフォルダ}/page.tsx
"use client";

import React from "react";
import { Button } from "@mui/material";
// import styles from "./page.module.css";

// App routerで利用する関数名は慣習的にPageにしたほうがよさそうです
// 埋め込みコンポーネントなどはIndexにするとフォルダ名のみで参照できる
export default function Page() {
  // ボタンで実行
  const doSomething = () => {
    // ブラウザのコンソールに
    console.log("mew");
  };

  return (
    <div>
      {{ /* ボタンをクリックすると関数が実行される */}}
      <Button variant="contained" onClick={doSomething}>
        ボタン
      </Button>
    </div>
  );
}

3-3. TypeScript の基本構文

TypeScirpt の基本構文とか、基本的な書き方について説明したいと思います。

3-3-1. 基本のデータ型、関数、配列、オブジェクト型、Record 型の利用方法

基本のデータ型、関数、配列、オブジェクト型、Record 型などの利用方法ついて説明します。

項目
const で変数宣言
let で変数宣言
var で変数宣言(非推奨)
基本のデータ型(string, number, boolean のみ) ※他に enum, symbol などもあり
配列
関数(function 利用)
関数(function 利用、無名関数)
関数(ES6 以降のアロー演算子を利用)
関数(ES6 以降のアロー演算子を利用、戻り値あり)
ユーザー定義型とデータ、その配列
Record 型 (ユーザー定義型を用意するまでもない時)

TypeScript:/app/program-code-sample/program-code1/page.tsx

/app/program-code-sample/program-code1/page.tsx
"use client";

import React, { JSX } from "react";
import { Button } from "@mui/material";
// import styles from "./page.module.css";

export default function Page(): JSX.Element {
  /*
   * 基本のデータ型、関数、配列、オブジェクト型、Record 型の利用方法
   *
   */
  const doSomething: () => void = (): void => {
    // # constで変数宣言(値の再代入不可)
    console.log("constで変数宣言 ----------");

    const message1: string = "mew1";
    console.log(message1 + "\n");

    // ------------------------------------------------------------

    // # letで変数宣言(値の再代入可)
    console.log("letで変数宣言 ----------");

    let message2: string = "mew2";
    message2 = "mew2";
    console.log(message2 + "\n");

    // ------------------------------------------------------------

    // # varで変数宣言(非推奨、再宣が可、関数スコープのため利用禁止)
    // var message3: string = 'mew3'
    // var message3: string = 'mew4' ※やばい

    // ------------------------------------------------------------

    // # 基本のデータ型
    console.log("基本のデータ型 ----------");

    const account: string = "neko";
    const age: number = 5;
    const isDeleted: boolean = true;
    console.log(account + "\n" + age + "\n" + isDeleted + "\n");

    // ------------------------------------------------------------

    // # 配列
    console.log("配列 ----------");

    const numberLists: number[] = [1, 2, 3];

    console.log(numberLists + "\n");

    // ------------------------------------------------------------

    // # 関数 (function利用)
    console.log("関数 (function利用) ----------");

    const doSomething1: (element: string) => void = function doSomething1(element: string): void {
      console.log(element);
    };
    doSomething1("mew5" + "\n");

    // # 関数 (function利用、無名関数)
    console.log("関数 (function利用、無名関数) ----------");

    const doSomething2: (element: string) => void = function (element: string): void {
      console.log(element);
    };
    doSomething2("mew6" + "\n");

    // # 関数 (ES6以降のアロー演算子を利用)
    console.log("関数 (ES6以降のアロー演算子を利用)) ----------");

    const doSomething3: (element: string) => void = (element: string): void => {
      console.log(element);
    };
    doSomething3("mew7" + "\n");

    // # 関数 (ES6以降のアロー演算子を利用、戻り値あり)
    console.log("関数 (ES6以降のアロー演算子を利用、戻り値あり) ----------");

    const doSomething4: (element: string) => string = (element: string): string => {
      element = "mew8";
      return element;
    };
    const message4: string = doSomething4("mew9" + "\n");
    console.log(message4);

    // ------------------------------------------------------------

    // # ユーザー定義型とデータ、その配列
    // eslint-disable-next-line no-restricted-syntax
    interface User {
      account: string;
      age?: number | null;
      isDeleted: boolean;
    }

    console.log("ユーザー定義型 ----------");

    const user: User = { account: "neko@gmail.com", age: 1, isDeleted: false };

    console.log(user["account"] + "," + user["age"] + "," + user["isDeleted"] + "\n");

    console.log("ユーザー定義型の配列 ----------");

    const users: User[] = [
      { account: "neko1@gmail.com", age: 1, isDeleted: false },
      { account: "neko2@gmail.com", age: 1, isDeleted: false },
    ];
    console.log(users);

    // ------------------------------------------------------------

    // # Record型 (ユーザー定義型を用意するまでもない時)
    console.log("Record型 ----------");

    const user2: Record<string, string> = { account: "neko@gmail.com", age: "1", isDeleted: "false" };

    console.log(user2["account"] + "," + user2["age"] + "," + user2["isDeleted"] + "\n");
  };

  return (
    <div>
      <div>プログラムコード1</div>
      <Button variant="contained" onClick={doSomething}>
        ボタン
      </Button>
    </div>
  );
}

ProgramCode1/page.tsx にサンプルコードを用意しています。

以降の ProgramCode2 ~ ProgramCode5 も含めて、慣れていない方は実際に手打ちで書いてみるのが練習になるので良いでしょう。ブラウザのボタンを押して、結果をブラウザのコンソールで確認することができます。

(基本のデータ型、関数、配列、オブジェクト型、Record 型 実行結果)
基本のデータ型、関数、配列、オブジェクト型、Record 型 実行結果

3-3-2. オブジェクトの参照コピーと値コピー

オブジェクト型は文字列や数字などの値型と異なり参照型のため、変数にイコールをすると参照コピーになる。(値が入るわけでなく、参照先が入る)。しかし、値コピーして利用したい場合があると思います。JSON.parse(JSON.stringfy(…))を使用すると新しい値としてコピーができます。

TypeScript:/app/program-code-sample/program-code2/page.tsx

/app/program-code-sample/program-code2/page.tsx
"use client";

import React, { JSX } from "react";
import { Button } from "@mui/material";
// import styles from "./page.module.css";

export default function Page(): JSX.Element {
  /*
   * オブジェクトの参照コピーと値コピーについて
   *
   */
  // ユーザー定義型
  // eslint-disable-next-line no-restricted-syntax
  interface User {
    account: string;
    age?: number | null;
    isDeleted: boolean;
  }

  const doSomething: () => void = (): void => {
    // 参照コピーの例、ageが2に更新されている
    console.log("参照コピー ----------");
    const user: User = { account: "neko@gmail.com", age: 1, isDeleted: false };
    const copyUser: User = user;
    user["age"] = 2;
    console.log(copyUser["age"]);

    // 値コピーの例、ageは1のまま変更なし
    console.log("値コピー ----------");
    const user2: User = { account: "neko@gmail.com", age: 1, isDeleted: false };
    const copyUser2: Record<string, string> = JSON.parse(JSON.stringify(user2));
    user2["age"] = 3;
    console.log(copyUser2["age"]);
  };

  return (
    <div>
      <div>プログラムコード2</div>
      <Button variant="contained" onClick={doSomething}>
        ボタン
      </Button>
    </div>
  );
}

(オブジェクトの参照コピーと値コピーについて 実行結果)
オブジェクトの参照コピーと値コピーについて 実行結果

3-3-3. テンプレートリテラル

テンプレートリテラルはバッククオートで囲んでいる部分のことで、バッククオート内の文字列に変数を埋め込むことができます。

TypeScript:/app/program-code-sample/program-code3/page.tsx

/app/program-code-sample/program-code3/page.tsx
    // # テンプレートリテラル
    const message1: string = "mew1";

    console.log("テンプレートリテラル ----------");

    console.log(`${message1}`);

3-3-4. スプレッド構文

スプレッド構文を利用すると配列同士を結合することができます。

TypeScript:/app/program-code-sample/program-code3/page.tsx

/app/program-code-sample/program-code3/page.tsx
    // # スプレッド構文
    // スプレッド構文を使わない配列の追加方法
    const numberLists1: number[] = [1, 2, 3];

    console.log("スプレッド構文を使わない配列の追加 ----------");

    numberLists1.push(4);

    console.log(numberLists1);

    // スプレッド構文で配列を結合
    const numberLists2: number[] = [1, 2, 3];
    const numberLists3: number[] = [4, 5];

    console.log("スプレッド構文 ----------");

    const numberLists4: number[] = [...numberLists2, ...numberLists3];

    console.log(numberLists4);

3-3-5. 配列の分割代入

配列から値をまとめて取得することができます。React では利用する機会が多いです。
しかし、分割代入はプログラムに慣れていても結構混乱します。(笑)

TypeScript:/app/program-code-sample/program-code3/page.tsx

/app/program-code-sample/program-code3/page.tsx
    // # 配列の分割代入
    // データ
    const users: string[] = ["neko1", "neko2", "neko3"];

    // 配列の分割代入 (先頭の2値のみ)
    console.log("配列の分割代入 ----------");

    const [username1, username2]: string[] = users;

    console.log(username1 + "," + username2);

3-3-6. オブジェクト型の分割代入

オブジェクト型でも分割代入ができます。ただしわかりずらくなるため、通常はこの方法を使用しないほうが良いです。

TypeScript:/app/program-code-sample/program-code3/page.tsx

/app/program-code-sample/program-code3/page.tsx
    // # オブジェクトの分割代入

    // ユーザー定義型
    // eslint-disable-next-line no-restricted-syntax
    interface User {
      account: string;
      age?: number | null;
      isDeleted: boolean;
    }

    // データ
    const user: User = { account: "neko@gmail.com", age: 1, isDeleted: false };

    // オブジェクトの分割代入
    console.log("オブジェクトの分割代入 ----------");

    const { account: ACCOUNT, age: AGE, isDeleted: IS_DELETED } = user;

    console.log(ACCOUNT + "," + AGE + "," + IS_DELETED);

3-3-7. オブジェクトの省略記法

変数名とプロパティ名が同一の場合は、オブジェクトの省略記法が利用できます。

TypeScript:/app/program-code-sample/program-code3/page.tsx

/app/program-code-sample/program-code3/page.tsx
    // # オブジェクトの省略記法

    // データ
    const account: string = "neko@gmail.com";
    const age: number = 1;
    const isDeleted: boolean = false;

    // オブジェクトの省略記法
    console.log("オブジェクトの省略記法 ----------");

    const user2: User = { account, age, isDeleted };

    console.log(user2["account"] + "," + user2["age"] + "," + user2["isDeleted"]);

3-3-8. オプショナルプロパティ、オプショナルチェインニング

ユーザー定義型のプロパティについて
プロパティ名の後ろに?をつけるのはオプショナルプロパティと呼び、プロパティが省略可能になります。さらに null を許容する場合は、型 | null のように、ユニオン型にする必要があります。

他の言語では必須 / 必須でないかは null かどうかのみになりますが、TypeScript ではプロパティの省略と、null を許容するかは別の設定となり、それぞれ適切に設定する必要があります。

オプショナルチェインニングでは、省略される可能性があるプロパティの後ろに?をつけるとプロパティがなくてもエラーせず安全に処理をしてくれます。しかしデータがない場合は undefined になるため、バグの原因にもなり、オプショナルチェインニングの方はできれば使わず、undefined にならないように処理する必要があります。

TypeScript:/app/program-code-sample/program-code3/page.tsx

/app/program-code-sample/program-code3/page.tsx
    // # オプショナルプロパティ、オプショナルチェインニング

    // ユーザー定義型
    // eslint-disable-next-line no-restricted-syntax
    interface User2 {
      account: string;

      // オプショナルプロパティ、データ型はnumber または null
      age?: number | null;

      isDeleted: boolean;
    }

    // データ
    const user3: User2 = { account: "neko@gmail.com", isDeleted: false };

    // オプショナルチェインニング
    console.log("オプショナルプロパティ、オプショナルチェインニング ----------");
    console.log(user3.account + "," + user3?.age + "," + user3.isDeleted);

上記の項目は ProgramCode3/page.tsx にサンプルコードを用意しています。

(テンプレートリテラル、スプレッド構文、配列の分割代入、オブジェクトの分割代入、オブジェクトの省略記法、オプショナルプロパティ・オプショナルチェインニング 実行結果)
テンプレートリテラル、スプレッド構文、配列の分割代入、オブジェクトの分割代入、オブジェクトの省略記法、オプショナルプロパティ・オプショナルチェインニング 実行結果

3-3-9. 条件分岐 (if ~ else)

条件分岐をしたいときに利用します。switch 文は if 文で代用できるので省略します。

TypeScript:/app/program-code-sample/program-code4/page.tsx

/app/program-code-sample/program-code4/page.tsx
    // # 条件分岐 (if ~ else文)
    const message1: string = "mew1";

    console.log("条件分岐 (if ~ else文) ----------");

    if (message1 === "mew1" || message1 === "mew2") {
      console.log("something-1-1");
    } else if (true) {
      // something
    } else {
      //something
    }

3-3-10. 等価・不等価演算子 (===, !==)

JavaScript/ TypeScript で等価・ 不等価演算子は「===」、「!==」のように 3 つの記号を並べたものを使用します。「==」、「!=」については緩い比較になるので非推奨です。ESLint でも通常は自動チェックが入ります。

TypeScript:/app/program-code-sample/program-code4/page.tsx

/app/program-code-sample/program-code4/page.tsx
    // # 等価・不等価演算子
    const message2: string = "mew1";

    console.log("等価・不等価演算子 ----------");

    if (message2 === "mew1") {
      console.log("something-2-1");
    }

    if (message2 !== "mew2") {
      console.log("something-2-2");
    }

    // ==、!= も動くが緩い比較になる(非推奨)
    /*
    const age = 1
    if (age === "1") {
      console.log("something-2-3");
    }
     */

3-3-11. 三項演算子

if 文の省略形で、「?」、「:」 で 1 行で書くことができます。

TypeScript:/app/program-code-sample/program-code4/page.tsx

/app/program-code-sample/program-code4/page.tsx
    // # 三項演算子
    const isChecked: boolean = true;

    console.log("三項演算子 ----------");

    const message3: string = isChecked ? "mew3" : "mew4";

    console.log(message3);

3-3-12. ループ (for 文)

ループを実行します。また、ループ中では break(ループの中断) / continue(次のループへ進) を使用することもできます。
※ループには do ~ while、while ~ もありますが、まずは for 文で理解してほしいので省略します。

TypeScript:/app/program-code-sample/program-code4/page.tsx

/app/program-code-sample/program-code4/page.tsx
    // # ループ (for文)
    console.log("ループ (for文) ----------");

    for (let j: number = 0; j < 5; j++) {
      console.log(j);
    }

    // # for + break
    console.log("for + break ----------");

    for (let j: number = 0; j < 5; j++) {
      if (j > 3) {
        break;
      }

      console.log(j);
    }

    // # for + continue
    console.log("for + continue ----------");

    for (let j: number = 0; j < 5; j++) {
      if (j === 1) {
        continue;
      }

      console.log(j);
    }

上記の項目は ProgramCode4/page.tsx にサンプルコードを用意しています。

(条件分岐、等価・不等価演算子、三項演算子、ループ(for 文) 実行結果)
条件分岐、等価・不等価演算子、三項演算子、ループ(for文) 実行結果

3-3-13. 利用機会の多い標準関数 (forEach 関数、map 関数、find 関数、filter 関数、reduce 関数)

配列を操作するときに利用機会の多い標準関数です。その他の標準関数は JavaScript ドキュメントを確認して下さい。

TypeScript:/app/program-code-sample/program-code5/page.tsx

/app/program-code-sample/program-code5/page.tsx
    // # forEach関数
    // forと同じループ、ループ中のbreak、continueは不可

    // データ
    const users: User[] = [
      { account: "neko1@gmail.com", age: 1, isDeleted: false },
      { account: "neko2@gmail.com", age: 2, isDeleted: false },
    ];

    console.log("forEach関数 ----------");

    users.forEach((element: User /*, index: number */) => {
      console.log(element["account"] + "," + 1 + "," + false);
    });

・map 関数
ループして要素数の同じ新しい配列を作成

TypeScript:/app/program-code-sample/program-code5/page.tsx

/app/program-code-sample/program-code5/page.tsx
    // # map関数
    // ループして要素数の同じ新しい配列を作成

    // データ
    const users2: User[] = [
      { account: "neko1@gmail.com", age: 1, isDeleted: false },
      { account: "neko2@gmail.com", age: 2, isDeleted: false },
    ];

    console.log("map関数 ----------");

    const newUsers: User[] = users2.map((element: User /*, index: number */) => {
      const user: User = {
        account: element["account"],
        age: 1,
        isDeleted: false,
      };

      return user;
    }, []);

    console.log(newUsers);

・find 関数
条件に一致した最初の 1 件を取得

TypeScript:/app/program-code-sample/program-code5/page.tsx

/app/program-code-sample/program-code5/page.tsx
    // # find 関数
    // 条件に一致した最初の1件を取得

    // データ
    const users3: User[] = [
      { account: "neko1@gmail.com", age: 1, isDeleted: false },
      { account: "neko2@gmail.com", age: 2, isDeleted: false },
    ];

    console.log("find 関数 ----------");

    // 見つかれば最初の1件を取得
    const found: User | undefined = users3.find((element: User) => element["account"] === "neko1@gmail.com");
    console.log(found);

    // 見つからなければundefined
    const found2: User | undefined = users3.find((element: User) => element["account"] === "neko3@gmail.com");
    console.log(found2);

・filter 関数
条件に一致した配列データを取得

TypeScript:/app/program-code-sample/program-code5/page.tsx

/app/program-code-sample/program-code5/page.tsx
    // # filter 関数
    // 条件に一致した配列データを取得

    // データ
    const users4: User[] = [
      { account: "neko1@gmail.com", age: 1, isDeleted: false },
      { account: "neko2@gmail.com", age: 2, isDeleted: false },
      { account: "neko3@gmail.com", age: 2, isDeleted: false },
    ];

    // return trueのアイテムを取得
    console.log("filter 関数 ----------");

    const filtered: User[] = users4.filter((element: User) => {
      return element["age"] === 2;
    });

    console.log(filtered);

・reduce 関数
ループして要素数の異なる新しい配列を作成 (accumulator に push したデータのみを作成)

TypeScript:/app/program-code-sample/program-code5/page.tsx

/app/program-code-sample/program-code5/page.tsx
    // # reduce 関数
    // ループして要素数の異なる新しい配列を作成 (accumulatorにpushしたデータのみを作成)

    // データ
    const users5: User[] = [
      { account: "neko1@gmail.com", age: 1, isDeleted: false },
      { account: "neko2@gmail.com", age: 2, isDeleted: false },
      { account: "neko3@gmail.com", age: 1, isDeleted: false },
    ];

    console.log("reduce 関数 ----------");

    const newUsers2: User[] = users5.reduce((accumulator: User[], element: User /*, index: number */) => {
      if (element["age"] === 1) {
        accumulator.push(element);
      }

      return accumulator;
    }, []);

    console.log(newUsers2);

上記の項目は ProgramCode5/page.tsx にサンプルコードを用意しています。

(forEach 関数、map 関数、filter 関数、find 関数、reduce 関数 実行結果)
forEach関数、map関数、filter関数、find関数、reduce関数 実行結果


3-4. 「import」と「export」 について

Node.js の JavaScript / Typescript では他のプログラムを読み込む時に import を使用し、他のコードで使用したいものがある時はそのファイルを export する必要があります。

Next.js では ページコンポーネント(.tsx)、または純粋な.ts ファイルの場合にそれぞれ、export、import をして利用します。

import は基本的に他のプログラミング言語と同じ使い方です。export には 2 種類の方法があります。名前を付けない default export と、名前を付ける named export です。

export の種類 説明 import 方法
default export 名前無しエクスポート、1 つのみ (App Router で利用するコンポーネント、SSR と CSR の境界のコンポーネントはこれを利用する) import Test from 'xxx'
named export 名前付きエクスポート、複数可 (以外のコンポーネントは named export を利用する) import { Test } from 'xxx'

Next.js の export については export の種類以外にも考慮が必要なことがあり、代表的な例を記載します。

(page.tsx - default export のみ)

page.tsx
// Next.jsのApp routerの扱うページはdefault exportのみ
// page.tsx直下にtemplate.tsxなどをCSRコンポーネントにする場合も境界のためdefault exportが必要

// React
import { JSX } from "react";

// App
import { Template } from "./template";

// default export
export default function Page(): JSX.Element {
  // something

  return <Template />
};

(organisms.tsx - named export OK)

organisms.tsx
"use client";

// 以外のコンポーネントは付けた名前で利用できるように、named exportを利用する

// React
import { JSX } from "react";

export const Template = (): JSX.Element => {
  // something

  return <>{/* something */}</>
};

また、export したものを import して利用することができます。default export は名前がないので名前を指定しますが、named export はすでにある名前を指定する部分が異なります。
ネットのサンプルコード等でライブラリなどを required を使って読込しているものがありますが、これは import で読み替えて利用できる場合があります。

named export の import (推奨)

page.tsx
// named exportのimport (named exportの名前を指定)
import { createUserService } from '@/service/createUserService';

default export の import (自由な名前を付けれるため、default export はなるべく避ける必要有り)

page.tsx
// default exportのimport (default exportは名前がないので、名前をここで指定)
import test from '@/service/createUserService';

3-5. TypeScript データ型の一覧

ここでは TypeScript で使うことができるデータ型について、使う機会が多いものについて説明します。

(プリミティブ型)

プリミティブ型とは基本的なデータ型のことです。そのまま値として扱われます。

データ型 説明
string 文字列型
number 数値型 -9007199254740991 ~ 9007199254740991 の範囲
boolean 真偽値型
undefined undefined 型
bigint 長整数型 -9007199254740991 ~ 9007199254740991 の範囲以上
symbol symbol 型 (毎回一意な値が生成される backend の DI などで利用)

(オブジェクト型)

複数の値や処理をひとまとまりにして扱える構造的なデータ型です。
プリミティブ型が値そのものなのに対して、オブジェクト型は、その複数の値や処理がある場所の参照先になります。

データ型 説明
string[]など 配列型
function () 関数型
Record<Keys, Type> キーと値の型をまとめて定義できるユーティリティ型。簡易的なオブジェクト型に便利
Record<Keys, Type>[] Record 型の配列
(interface) User など インターフェース型 (ユーザー定義型)
(class) User など クラス型、backend の entity などで利用
User[] など オブジェクトの配列
Xx<T> Generics 型、型引数を使って柔軟な型定義ができる
Xx<User> など Generics 型

(type は プリミティブ型、オブジェクト型のどちらでも使える型定義構文)

type については 2 パターンの使い方があり、リテラル型、またはオブジェクト型としての定義です。
※ただし、このページのサンプルでは type でなく、主に interface を利用してオブジェクト型を定義しています。

データ型 説明
type Color = 'A' | 'B' | 'C' など リテラル 型(文字リテラル型) ※特殊なプリミティブ型
(type) User など オブジェクト型として定義が可能

(型の合成)

データ型 説明
string | null など ユニオン 型 ※OR 条件のデータ型、よく利用する
string & null など インターセクション型 ※AND 条件のデータ型、AND のため使う機会は少ない

(特殊な型)
特殊な型を表現するものです。

データ型 説明
void void 型、関数で return をしないもの
unknown unknown 型、型が不定の時、型チェックをして安全に扱える型、例外処理の catch 時などで利用している
never never 型、到達不可を表現するためのもの
any any 型、何でも型、基本的には使用禁止

その他、パッケージでは独自の関数の型があり、パッケージで同時に導入可能であったり、分かれているものがあるため注意が必要です。
package.json の devDependencies で "@types/"で始まっているものなど、パッケージと一緒に必要なものがあります。

(package.json の@types パッケージの例)

package.json
  "devDependencies": {
    "@types/luxon": "^3.6.2",
    "@types/node": "^22.15.23",
    "@types/react": "^19.1.6",
    "@types/react-dom": "^19.1.5",
    // ...
  }

3-6. ユーザー定義型の管理方法

アプリ作成では、独自に定義した interface 型(ユーザー定義型)を共通の場所にまとめて管理をすることで、いろいろな場所から参照することができます。

具体的には types フォルダを用意して、下記のような形式で interface 型を定義します。
※type を利用する場合はあるかもしれませんが、このページでは interface 型で統一しています。

利用場面としては、一覧表示、登録画面や API リクエスト・レスポンスなどで利用することが多いです。

サンプルでは この interface 型(ユーザー定義型)と合わせて、バリデーション用の Schema も記載しています。
User 型を例として使いまわしをしていますが、目的・用途によっては User 型に似た構造の型を用意するケースもあります。

TypeScript:/types/User.ts

/types/User.ts
// ユーザー定義型
// 必須でないプロパティはオプショナル"?"、ユニオン型でnullを許可
// 登録・更新等でデータ型が分かれる場合がある
export interface User {
  account: string;
  username: string;
  password?: string | null;
  age?: number | null;
  hobby?: string | null;
  applyDate?: string | null;
  isEnabled?: boolean | null;
  remarks?: string | null;
  isDeleted?: boolean | null;
  sortOrder?: number | null;
  createdAt?: string | null;
  updatedAt?: string | null;
  createdBy?: string | null;
  updatedBy?: string | null;
}

4. Web アプリの概念説明

4-1. Web アプリ、HTTP リクエスト / レスポンスの概念について

Web アプリはどのように動いているかについて簡単に説明します。Web アプリは、サーバー上ではプログラム実行環境 + HTTP サーバー上で実行しています。ブラウザ画面からサーバーにリクエスト(要求)をして、サーバー側で処理した結果のレスポンスを取得して、ブラウザ画面に結果を表示するという仕組みとなっています。

いまのインターネット上の Web サイトでシステムをつかっているサイトは、見えない部分でこのような動きをしています。

また、Web ページを表示する方法は、下記の図に記載している中でサーバーサイドレンダリングについては昔からよく利用されています。現在ではその他にもいろいろページのレンダリング方式があります。

(いろいろある Web アプリ画面の表示方法)
いろいろあるWebアプリ画面の表示方法

(レンダリング方式のメリット・デメリット)

項目 メリット・デメリット
SSR サーバー側で HTML・データを組立するため、若干負荷がかかる。
インターネット上の Web サイト向き。
CSR 画面の動きがスムーズ。サーバー負荷小。開発難易度が高い。
インターネット上の Web サイトには向かない。
SSG HTML が生成済みのものを利用するので高速表示。サーバー処理が不要。リアルタイム性がなく、更新は再ビルド必要。
ISR SSG の欠点を補い、部分的に動的にしたもの。
ISR という名前は Next.js 固有。

4-2. Next.js アプリのレンダリング方式

Next.js アプリ では 4 つのレンダリング方式を組み合わせて部分的に利用することができます。
デフォルトは SSR (RSC: React Server Component) の tsx のページとなっており、その中に動的な CSR のコンポーネントを入れて使うのが基本イメージとなります。

簡単な例では各ページの一番外枠を SSR としてレンダリングし、その中を UI パーツ を CSR で利用することで、CSR のデメリットをカバーしているように見えます。

より厳密にリアルタイム性が求められる場合は、他のレンダリングを使う場合があるかもしれませんが、とりあえず細かいことは気にしなくて良いかもしれません。Next.js を使うことで大体のことは解決してくれます。

(Next.js アプリの SSR + CSR 基本イメージ)
Next.js アプリの SSR + CSR 基本イメージ

サンプルコードにも記載していましたが、tsx の先頭に "use client"; と記載しているのが CSR として利用することになります。


4-3. frontend / backend の API で利用する HTTP メソッドの種類について

従来の SSR などで構築された 1 プロジェクトの Web アプリケーションは クライアント側から GET / POST のみで、Controller メソッドパスを直接実行する形式が今もよくあります。

また、サーバー側を backend として API 専用として利用する場合は、GET、POST、PATCH、DELETE の 4 つのメソッドを使用して API を構築し、それに対して frontend 側からは API URL と HTTP メソッドを利用してリクエストをしています。

従来の SSR 等アプリでメソッドパス(複数有り)だと複雑になりがちですが、backend を API 専用として API URL(1 つのみ)にしたほうがメンテナンス性はいいのかなと思います。

Node.js では OpenAPI という API の使用を決める規格を基準に定義を作成していく場合が多いです。

アプリ クライアント サーバー側
従来の SSR 等アプリ GET、POST、 Controller メソッドパス(複数有り)が多かったイメージ クラス型 Controller
比較して Next.js の backend GET、POST、PATCH、DELETE ※API URL(1 つのみ) 関数 or クラス型 API

4-4. JSON データについて

SSR ではデータを含む HTML データ、またはデータのみを返すことを選択できますが、API ではデータのみを返します。データのみを返すときによく使われるのが、JSON(JavaScript Object Notation) 形式のデータになります。構文は JavaScript におけるオブジェクトの表記法に由来しています。

TypeScript の基本にて、オブジェクトや配列オブジェクトのサンプルコードがありましたが、その時のデータを JSON 形式に変換して利用することができます。

次のサンプルでは、データを JSON 文字列に変換(JSON シリアライズ)、JSON 文字列をデータに変換(JSON デシリアライズ)の、JSON の相互変換について記載しています。

TypeScript:/app/json-convert-sample/page.tsx

/app/json-convert-sample/page.tsx
"use client";

import React, { JSX } from "react";
import { Button } from "@mui/material";
// import styles from "./page.module.css";

// ユーザー定義型
interface User {
  account: string;
  age?: number | null;
  isDeleted: boolean;
}

export default function Page(): JSX.Element {
  /*
   * データをJSON文字列に変換 (JSONシリアライズ)、JSON文字列をデータに変換 (JSONデシリアライズ)
   *
   */
  const doSomething: () => void = (): void => {
    // データ
    const users: User[] = [
      { account: "neko1@gmail.com", age: 1, isDeleted: false },
      { account: "neko2@gmail.com", age: 2, isDeleted: false },
    ];

    // データをJSON文字列に変換
    console.log("データをJSON文字列に変換 (JSONシリアライズ) ----------");

    const jsonText: string = JSON.stringify(users);

    console.log(jsonText);

    /* コンソールに表示
    [{"account":"neko1@gmail.com","age":1,"isDeleted":false},
    {"account":"neko2@gmail.com","age":2,"isDeleted":false}]
     */

    // JSON文字列をデータに変換
    console.log("JSON文字列をデータに変換 (JSONデシリアライズ) ----------");

    const users2: User[] = JSON.parse(jsonText);

    console.log(users2);
  };

  return (
    <div>
      <div>プログラムコード6</div>
      <Button variant="contained" onClick={doSomething}>
        ボタン
      </Button>
    </div>
  );
}

(データを JSON 文字列に変換、JSON 文字列をデータに変換 実行結果)
データをJSON文字列に変換、JSON文字列をデータに変換 実行結果


4-5. Node.js その他のフレームワーク

Node.js ではその他のフレームワークも利用することができます。筆者は React、Vue.js、Express.js、Next.js、Nest.js をさらっと触ってみた感想として、frontend、backend を含めて、Next.js が一番使いやすいイメージでした。

Next.js では frontend、API (backend) の両対応ができる部分、その他の標準機能が便利なことと、ビルドツールを自前で構築したくないこともあり、最初から入っている next コマンドをそのまま使えてしまうことが理由です。

分類 フレームワーク
frontend 用 React、Next.js、Vue.js、Nuxt.js、Angular、他
backend 用 Express.js、Next.js、Nest.js、他
フルスタック利用 Next.js、Nuxt.js

※フルスタック利用 = frontend + backend を区別なく 1 プロジェクトで利用する意味です。


5. Next.js について

ここでは Next.js の基本的な使い方、コンポーネント、Props、Next.js で使用している React フックについて学びます。

5-1. Next.js が持っている機能

Next.js は最初から開発に必要な機能を持っているフレームワークであり、プロジェクト作成時に最初から設定済みで、導入もスムーズです。
純粋な React では単独でできなかったり手間がかかることも多いのですが、
Next.js を利用するメリットとしては次の表のイメージになります。

機能 説明
TypeScript 標準対応 プロジェクト作成時から設定済みですぐに利用可能
ESLint・Prettier の統合 プロジェクト作成時から設定済みですぐに利用可能
ビルドツール自動設定 プロジェクト作成時から設定済みですぐに利用可能、dev で turbopack 利用可
レンダリング方式の選択利用 SSR ベース + 部分的に CSR を使っているイメージ、部分的に SSR / ISR も利用可
Layout 機能 共通の layout.tsx を利用
App Router /app/{フォルダ}/page.tsx でルーティング(URL)を自動化
API Routes /app/api/{フォルダ}/route.ts で API ルートを作成
フルスタック対応 API 利用できるので 1 プロジェクトで構築可 ※backend は分けたほうがいいけど
画像最適化(next/image) 画像を自動で最適な形に加工・配信
自動コード分割 コードを自動分割してくれるらしい

5-2. Next.js の基本

ここでは Next.js が実行される方法と、Next.js の設定ファイルについて説明します。

5-2-1. Next.js を実行した時の動き

Next.js はプログラム実行時に /app/layout.tsx を実行し、/app/page.tsx をブラウザに表示します。
.tsx 内で return した JSX.Element (HTML のようなもの) を画面上に表示します。
最初に表示する画面を変更するときは、コンポーネントを差し込むか、リダイレクトをするなどして変更します。

(Next.js 画面表示の方法)
Next.js 画面表示の方法

この .tsx ファイルをコンポーネントとも呼び、タグとして使ったり、props という方法で値や関数を渡して子コンポーネントで使っていく方法が、Next.js の基本的な画面の作り方にもなります。
props については後項で説明します。

下記のサンプルは Next.js で 1 画面を作成したときのイメージで、1 画面は複数のコンポーネントから構成されていることが多いです。
画面は ATOMIC デザインと呼ばれる方法で作ることがあり、その一例として 1 画面の構成イメージを記載します。

(Next.js 1 画面)
Next.js 1画面


5-2-2. .env、next.config.ts の設定方法

Next.js アプリ内で利用する設定を記載する場所は、.env と next.config.ts の 2 つあります。
.は環境変数を書くファイルとも呼ばれ、通常は .env を使用しますが、新規のプロジェクトには含まれていませんので新しいファイルから作成します。

next.config.ts ではビルド設定を書くこともできますが、基本的にビルドは Next.js 標準で自動的に行ってくれるので、基本的にこちらの設定は不要です。

環境変数名は、先頭に "NEXT_PUBLIC_" プレフィックスを先頭につけて公開するものと、"SECRET_" をつけて非公開として扱うものがあります。(SECRET_は、非公開キーや、データベース接続文字列などで利用します)

(.env)

.env
# 共通キー
NEXT_PUBLIC_API_URL: "http://localhost:5000/api"

# 外部認証など非公開にしたい値ー
SECRET_GOOGLE_CLIENT_ID=xxx
SECRET_GOOGLE_CLIENT_SECRET=yyy

(利用方法)

// stringに入れる時は ?? で undefined 確認をしてから代入する必要があります。
const PUBLIC_API_URL: string = process.env.NEXT_PUBLIC_API_URL ?? "";

5-3. コンポーネントと Props の概要

前項で説明した通り、.tsx 内で return した JSX.Element (HTML のようなもの)をコンポーネントと呼びます。
Next.js でコンポーネントを使えるようにするためにはコンポーネントと Props の使い方を覚える必要がありますが、まずはここでコンポーネントと Props の概要を説明します。

コンポーネントは大きく分けると 2 種類のコンポーネントがあります。

コンポーネント 説明 レンダリング方法
ページを表示するためのコンポーネント page.tsx のこと 基本的に SSR
UI コンポーネント 画面の一部、template.tsx、table.tsx、modalForm.tsx、button.tsx など、入れ子になっている 基本的に CSR

画面上で動きのある要素、ボタンをクリックしたら何かしたいなどは CSR で UI コンポーネントの作成が必要になります。
UI コンポーネントはコードの先頭に "use-client";を宣言することで CSR となります。
また、return の中で開始タグ、終了タグを使わない場合は、空タグで囲む必要があります。

コードの例として下記に記載します。ここでは実際の動作確認はせずにイメージを掴むのみとしてください。

(利用方法)

/app/users/page.tsx
// ページを表示するコンポーネント(基本的にSSR)
import { JSX } from "react";

// Page (Routing)
export default function Page(): JSX.Element {

  // JSX.Element (Html)
  return (
    <>
      <Template />
    </>
  );
}

ページを表示するコンポーネントの中に、入れ子で UI コンポーネントを追加します。
画面を作成する時に Next.js では、React の useXx というフックを利用したり、コンポーネントを作成していきます。

(利用方法)

/app/users/template.tsx
"use client";

// UIコンポーネント(基本的にCSR)
import { JSX, useState, useEffect, useCallback } from "react";
import { API_URL } from "@/config/env";
import { Table } from "./table";
import { ModalForm } from "./modalForm";

// Template (ページレイアウト & stateを持つ場所)
// SSRのpageからPropsの受け取りは不可
export const Template = (): JSX.Element => {
  const apiUrl: string = API_URL;

  // Stateの一例
  const [rowData, setRowData] = useState<User[]>([]);
  const [data, setData] = useState<User | null>(null);

  // Something
  const onRegister = useCallback(async (user: User) => {
    // Something
  }, []);

  // JSX.Element (Html)
  return (
    <>
      子コンポーネント
      {/* UIコンポーネント内のUIコンポーネントの一例 */}
      <Table rowData={rowData} onRowDoubleClick={onRowDoubleClick} />
      <ModalForm data={data} onRegister={onRegister} onClose={onClose} />
    </>
  );
}

UI コンポーネントの中に、さらに UI コンポーネントを追加したイメージです。

(利用方法)

/app/users/modal-form.tsx
"use client";

// UIコンポーネント(基本的にCSR)
import { JSX, useEffect } from "react";
import { User } from "@/types/User";
import { Box, Button } from "@mui/material";

// IProps
interface IProps {
  data: User | null;
  onRegister: (user: User) => Promise<void>;
}

// Organisms
export const Children = function (props: IProps): JSX.Element {
  const { data, onRegister, onClose }: IProps = props;

  return (
    <>
      <Box>子の子コンポーネント</Box>
      <Button size="small" variant="contained" color="primary" onClick={() => onModalRegister}>
        登録
      </Button>
    </>
  );
};

5-4. スタイルとイベントの使い方

Props、コンポーネントを覚える前に、Next.js (React 含む) 特有のスタイルとイベントの使い方を説明します。

スタイルについては通常の CSS の書き方と若干異なり、直接スタイルを記述する場合は、下記コードのようになります。

(スタイルの記述方法)
・スタイルを {{}} のように二重括弧で囲む
・ハイフンでつながっているスタイル名をキャメルケースに変換する必要がある (例)border-color -> borderColor
・値はクオートで囲む必要がある
・セミコロンは使用できない、各設定はカンマで区切る必要がある

MUI のコンポーネントにスタイルを書く場合、"sx" を利用します。

(利用方法)

"use client";

import { JSX } from "react";
import { Box } from "@mui/material";

export default function Page(): JSX.Element {
  return (
    <>
      {/* 直接 style を書く場合 */}
      <div style={{ color: "red", borderColor: "red" }}>TEST</div>

      {/* MUI コンポーネントに style を書く場合は sx を利用する */}
      <Box sx={{ color: "red", borderColor: "red" }}>TEST2</Box>
    </>
  )
}

スタイルは書く場所はいくつかありますが、一例ですが次の基準で分けると良いでしょう。

スタイルを書く場所 説明
global.css layout.tsx や、body、H1 など共通で利用するもののみ
MUI を利用する UI コンポーネント コンポーネント内で MUI コンポーネントを利用し、sx で指定
page.module.css ※ページ単位で利用するものだが、MUI を利用するので基本的に不要

イベントについては、通常イベントに似ていますが記述する場合は、下記コードのようになります。

(ボタンの記述方法)
・コードの 1 行目に "use client"; を記載し CSR にする必要がある。
・イベントで実行する関数を作成する。
・イベントを{}のように二重括弧で囲む
・イベント名は小文字だけのことが多いが、キャメルケースに変換する必要がある (例)onclick -> onClick

(利用方法)

"use client";

import { JSX } from "react";

export default function Page(): JSX.Element {
  const onClick = (event: Event): void => {
    console.log("Something");
  }

  return (
    <>
      <button onClick={onClick}>ボタン</button>
    </>
  )
}

イベントについて様々はありますが、入力項目やボタンで利用する代表的なものとして以下のようなものがあります。

イベントハンドラ 説明
onClick シングルクリック
onChange 値が更新された時
onSelect 選択
onDrag ドラッグ
onClick ドロップ
onFocus フォーカスが当たった時
onBlur フォーカスが失われた時

5-5. コンポーネントと Props で子コンポーネントへの値の渡し方

ここではコンポーネントと Props の使い方について説明します。
コンポーネントから下位層のコンポーネントへ値・状態・関数を渡す場合は、 Props という機能を利用します。

下記のコードに Props の簡単な利用方法を記載しました。
画面を実行して、子コンポーネントに値が渡っていることを確認しましょう。

TypeScript:/app/props-hook-sample/props/page.tsx

/app/props-hook-sample/props/page.tsx
"use client";

import { JSX } from "react";

export default function Page(): JSX.Element {
  console.log("レンダリング発生(2回実行)");

  return (
    <>
      <div style={{ border: "1px gray solid", padding: "10px" }}>
        Propsサンプル
        <div style={{ padding: "10px" }}>
          <Children message="mew" />
        </div>
      </div>
    </>
  );
}

// 子コンポーネントは実際は別ファイルに書きますが、説明のため1ファイルに記載します。
// IProps (子コンポーネントで受け取るデータ型)
interface IProps {
  message: string;
}

const Children = function Children(props: IProps): JSX.Element {
  // Propsを受け取る時はオブジェクトの分割代入を利用する
  const { message } = props;

  return (
    <>
      <div style={{ border: "1px gray solid", marginTop: "10px", padding: "10px" }}>
        Childrenコンポーネント
        <div>propsで受け取ったmessage: {message}</div>
      </div>
    </>
  );
};

(Props 実行結果)
Props 実行結果


5-6. React フックの概要

Next.js は、見た目を作るために JSX という書き方を使い、その中で React を利用しています。
そして、画面に「動き」や「変化」をつけたいときには、React のフックを使って「状態」や「タイミング」を操作する必要があります。

React フックという名前から入ってもピンとこないですが、どんなことをしたいかを考えるとイメージがつくかもしれません。
例ですが、このような時に React フックが必要になります。

・ボタンを押して値を更新する (useState)
・ページを開いたときにデータを読み込む (useState)
・入力欄に自動でカーソルを入れたい (useRef)
・親から子コンポーネントに関数を渡して使いたい時 (useCallback)

HTML だけで作るページと比べると少し難しく感じるかもしれませんが、Next.js で画面を作るためには、React フックが必須になります。

基本的に、下記の 4 つのフック + コンポーネントのメモ化(memo) のみでコンポーネントを構築することができます。

(代表的な React フック一覧)

React フック 役割 説明
useState 状態管理 (state) 値と関数の状態管理をするもの、値を変更するたびに再レンダリング
useRef 値の保持 コンポーネント内で一時的に値を保存、値を変更しても再レンダリングしない
※useState と反対の性質
useEffect 副作用 画面表示時、または値が変わった時に実行
useCallback 関数のメモ化 Props で子コンポーネントに関数を渡して使いたい時など

パッケージによっては独自のフックを持っている場合もあります。

開発元・パッケージ React 向けフック 役割 説明
React Hook Form useForm Form state 管理 入力フォームの状態管理を行う
Next.js useSWR、useSWRMutation HTTP リクエスト GET / POST / PATCH / DELETE の HTTP リクエストを行う

Next.js の useSWR 系フックはキャッシュが利用できるなどいろいろ高機能ですが、ここでは使わずに、HTTP 陸セストは カスタムフック・axios で実施します。
詳細手順は後項に記載します。

(その他の React フックについて一例)

React フック 役割 説明
useMemo 変数のメモ化 変数のメモ化、稀に利用する
useReducer 状態管理 (state) 複雑な状態管理が必要なとき ※可能な限り useState を優先したほうがよい
useDebugValue 開発者用の出力 ※利用後は削除すること
useContext グローバル状態管理 上位ページが SSR の場合、利用機会ができなかった
useLayoutEffect 副作用(DOM 描画直前) ※基本的に使用しない
useImprerativeHandle ref 制御? ※基本的に使用しない

次から代表的な React フックの使い方を覚えて、画面を作る時に利用できるようにしましょう。


5-7. state 管理 (useState)

React では、画面に表示する値や今の何かの状態を記憶しておく仕組みとして useState という機能があります。
普通の変数(let や const)だと毎回リセットされてしまいますが、一方、useState で作った変数は、値を変えるとその変化に応じて画面も再レンダリングし、画面の表示を更新してくれます。
今の状態の変数を保持する方法と覚えておくとよいでしょう。

(通常の変数宣言と useState の違いイメージ)

項目 説明
const x, let x 通常の変数、プログラム実行後は残っていない。
useState(x, setX) 今の状態の変数、状態を変更する関数

書き方としては、左辺に const [x, setX]の形で入力し、右辺の useState<>にデータ型、()に初期値を入力します。
値をセットする方法は setX()です。

(利用方法)

const [count, setCount] = useState<number>(0);
setCount(1);

下記のコードに useState の簡単な利用方法を記載しました。
画面を実行して、useState の動作を確認しましょう。

ボタンを実行して値を変更するとコンポーネントを再レンダリングするため、画面が更新されているのが確認できます。
通常の変数では画面はリロードしないと更新されないですが、useState では自動的に表示を更新してくれます。
開発モードではレンダリングのたびに同じものが 2 行出力されます。(開発モードのチェック機能のため)

下記コードの例には含まれていませんが、 useState を Props で子コンポーネントに渡して利用することが多いです。

TypeScript:/app/props-hook-sample/usestate/page.tsx

/app/props-hook-sample/usestate/page.tsx
"use client";

import { JSX, useState } from "react";

export default function Page(): JSX.Element {
  // フックを利用する時は配列の分割代入を利用する
  // count: Stateの変数名
  // setCount: Stateに設定する関数名
  const [count, setCount] = useState<number>(0);
  console.log("レンダリング発生(2回実行)");

  const handleClick = () => {
    console.log("handleClick ----------");
    setCount((count: number) => count + 1);
  };

  return (
    <>
      <div style={{ border: "1px gray solid", padding: "10px" }}>
        useStateサンプル
        <div style={{ padding: "10px" }}>
          <div>useStateのcount: {count}</div>
          <div>useStateを更新する度に再レンダリングが発生するため画面が更新される。</div>
          <div>
            <button onClick={handleClick}>ボタン</button>
          </div>
        </div>
      </div>
    </>
  );
}

(useState 実行結果)
useState 実行結果


5-8. 値の保持 (useRef)

useRef を利用することでそのコンポーネント内で値の保持をすることができます。
useState と 反対の性質を持ち、値を更新しても、画面の再レンダリングを行いません。
利用場面としては画面は更新せずに、input にフォーカスを当てたり、入力状態を記録したい時など、レンダリングをせずに裏で値を記憶しておきたい時に利用します。

useRef を使うことで、そのコンポーネント内で値を保持し続けることができます。
useState とは反対の性質を持ち、値を更新しても画面の再レンダリングは発生しません。
主な利用場面は、画面に表示せずに裏で値を記録したいときや、input 要素にフォーカスを当てる、値を取得するなど、DOM 操作を行いたい時などです。
表示には関係しないけど、値を覚えておきたい時に利用します。

書き方としては、左辺に const x の形で入力し、右辺の useRef()に初期値を入力します。
値をセットする方法は x.current = です。

(利用方法)

const x = useRef<number>(0);
x.current = 2;

下記のコードに useState と useRef の動きを比較できるサンプルを記載しました。
useState は値を更新すると再レンダリングが発生するのに対し、useRef では値を更新しても再レンダリングが発生しないことが確認できます。

TypeScript:/app/props-hook-sample/useref/page.tsx

/app/props-hook-sample/useref/page.tsx
"use client";

import { JSX, useState, useRef } from "react";

export default function Page(): JSX.Element {
  const [count, setCount] = useState<number>(0);
  const count2 = useRef<number>(0);
  console.log("レンダリング発生(2回実行)");

  // useState
  const handleClick = () => {
    console.log("handleClick、useStateを更新 ----------");
    setCount(count + 1);
    console.log(count);
  };

  // useRef
  const handleClick2 = () => {
    console.log("handleClick2、useRefを更新 ----------");
    count2.current = count2.current + 1;
    console.log(count2.current);
  };

  return (
    <>
      <div style={{ border: "1px gray solid", padding: "10px" }}>
        useRefサンプル
        <div style={{ padding: "10px" }}>
          <div>useStateのcount: {count}</div>
          <div>useStateを更新する度に再レンダリングが発生するため画面が更新される。</div>
          <div>
            <button onClick={handleClick}>useStateのボタン</button>
          </div>
          <div style={{ marginTop: "10px" }}>useRefのcount2: {count2.current}</div>
          <div>内部値の更新のみ、再レンダリングが発生するまで画面は更新されない。</div>
          <div>
            <button onClick={handleClick2}>useRefのボタン</button>
          </div>
        </div>
      </div>
    </>
  );
}

(useRef 実行結果)
useRef 実行結果


5-9. 副作用(useEffect)

ページを開いたときや、何かが変わったとき、自動で動く処理をしたい時があります。
例えば、画面を開いたときの初期表示でデータを取得する場合などです。
この時、副作用(useEffect) を利用して更新をすることができます。
useEffect は主に画面の初期表示に使うことが主ですが、中に state を持たせた場合は、連動して都度実行されます。

書き方としては、useEffect で囲み、useEffect の第 1 引数に関数、第 2 引数に依存配列(useEffect 内で利用する変数を配列形式で指定)の形で入力します。

(利用方法)

useEffect(関数, [依存配列]);

TypeScript:/app/props-hook-sample/useeffect/page.tsx

/app/props-hook-sample/useeffect/page.tsx
"use client";

import React, { JSX, useState, useEffect } from "react";

export default function Page(): JSX.Element {
  const [count, setCount] = useState<number>(0);
  const [count2, setCount2] = useState<number>(0);
  console.log("レンダリング発生(2回実行)");

  // 初期表示
  useEffect(() => {
    console.log("useEffect、初回のみuseStateを更新 ----------", setCount(99));
  }, [count]);

  // 値を変更した時
  useEffect(() => {
    console.log("useEffect2、useStateを更新 ----------", count2);
  }, [count2]);

  const handleClick = () => {
    setCount2(count2 + 1);
    console.log("handleClick: ", count2);
  };

  return (
    <>
      <div style={{ border: "1px gray solid", padding: "10px" }}>
        useEffectサンプル
        <div style={{ padding: "10px" }}>
          <div>useEffectのcount: {count}</div>
          <div>画面の初回表示時のみレンダリング</div>
          <div style={{ marginTop: "10px" }}>useEffectのcount2: {count2}</div>
          <div>更新する度に再レンダリングが発生するため画面が更新される。</div>
          <div>
            <button onClick={handleClick}>ボタン</button>
          </div>
        </div>
      </div>
    </>
  );
}

(useEffect 実行結果)
useEffect 実行結果


5-10. 再レンダリングが発生する条件

React フックのサンプルでレンダリングの発生について簡単に確認しましたが、ここでは詳細について説明します。
レンダリングが行われるパターンはいくつかありますが、レンダリングが毎回発生してしまうとレスポンスが遅くなってしまうので、取り扱いに注意して利用する必要があります。

再レンダリングが発生する条件としては以下があります。

(再レンダリングが発生する条件)

項目
State が更新された時
親コンポーネントが再レンダリングされた時
Props が更新された時
Context の値が更新された時
その他

5-11. メモ化

メモ化とは前回の処理結果を保持しておくことでパフォーマンスを向上させるために利用する機能です。
コンポーネントをメモ化することで、必要のない再レンダリングを避けることができます。

例えば普通の TypeScript のアプリだとただの関数を書くだけのことですが、React だと 子コンポーネントに渡す関数を使い回しをする場合は useCallback でラップする必要があります。
React では余計なレンダリングが発生しないように、コンポーネント、場所によって使い分けをする必要があります。

メモ化には次の 3 つの種類があります。

(メモ化で使うフック)

機能 説明
memo コンポーネントのメモ化 (フックではない)
useCallback コンポーネント内の関数のメモ化
useMemo コンポーネント内の変数のメモ化

memo と useCallback はよく使うことが多いです。useMemo はあまり使う機会が少ないので省略します。

5-12. コンポーネントのメモ化 (memo)

子コンポーネントを memo でメモ化をすると、親のコンポーネントを再レンダリングしても、子コンポーネントの不要な再レンダリングを防ぐことができます。Props に変更がなければ再レンダリングを避けることができます。

書き方としては、コンポーネントの関数を memo で囲ってるのみです。
memo を無名関数に使うとエラーするため、必ず function Children のような形の関数にしてから memo で囲む必要があります。

(利用方法)

const Children = memo(function Children(props: IProps): JSX.Element {
  // Something
});

下記のコードでは memo の無し・有りの子コンポーネントを比較で載せています。memo 有りの子コンポーネントは再レンダリングされていないことを確認できます。
template 下の organisms のコンポーネントについては、基本的に memo でメモ化しておくとよいです。

TypeScript:/app/props-hook-sample/memo/page.tsx

/app/props-hook-sample/memo/page.tsx
"use client";

import { JSX, memo, useState } from "react";

export default function Page(): JSX.Element {
  const [count, setCount] = useState<number>(0);
  console.log("レンダリング発生(2回実行)");

  return (
    <>
      <div style={{ border: "1px gray solid", padding: "10px" }}>
        memoサンプル
        <div style={{ marginTop: "10px", padding: "10px" }}>
          <div>count: {count}</div>
          <div>memo無しChildren1コンポーネント:親の再レンダリングが発生すると、子の再レンダリングも発生する。</div>
          <div>memo有りChildren2コンポーネント:親の再レンダリングが発生しても、子の再レンダリングは発生しない。</div>
          <div>
            <button onClick={() => setCount(count + 1)}>ボタン</button>
          </div>
          <Children1 message="mew" />
          <Children2 message="mew2" />
        </div>
      </div>
    </>
  );
}

interface IProps {
  message: string;
}

// memo無し
const Children1 = function Children(props: IProps): JSX.Element {
  const { message } = props;
  console.log("Children1のレンダリング発生(2回実行)");

  return (
    <>
      <div style={{ border: "1px gray solid", marginTop: "10px", padding: "10px" }}>
        memo無しChildren1コンポーネント
        <div>propsで受け取ったmessage: {message}</div>
      </div>
    </>
  );
};

// memo有り
const Children2 = memo(function Children2(props: IProps): JSX.Element {
  const { message } = props;
  console.log("Children2のレンダリング発生(2回実行)");

  return (
    <>
      <div style={{ border: "1px gray solid", marginTop: "10px", padding: "10px" }}>
        memo有りChildren2コンポーネント
        <div>propsで受け取ったmessage: {message}</div>
      </div>
    </>
  );
});

(memo 実行結果)
memo 実行結果


→ Next.js & TypeScript 利用ガイド (フロントエンド 編) - Page 2 / 3 へ続く
https://zenn.dev/mofuweb/articles/nextjs-typescript-guide-1-2

Discussion