2025-3I プログラミング3 第04回 講義資料

2025年10月23日(木)5・6時限

1 情報共有

2 ここまでの流れ

後期中間試験ぐらいまでの授業の流れ (予定)

  1. モダンTypeScript基礎学習のための環境構築
  2. TypeScript基礎学習
  3. Reactを使ったTodoアプリのための環境構築 ← 今回 (前半)
  4. Todoアプリ開発(Reactフロントエンド開発)のチュートリアル ← 今回と次回
  5. Todoアプリのカスタマイズや作り込み → 課題1

まずは、次のような📝Todoアプリの開発 (Reactを採用したフロントエンド開発) を目標とします。

今回講義の 後半の内容 は、前回講義で宿題として示した【React入門】完全初心者OK!1から簡単なTodoアプリを作ってReactの1歩を踏み出してみよう ~Reactチュートリアル~の視聴を前提とした内容です。

3 React開発環境の構築

Viteというビルドツールを使用して「Tailwind CSS」と「TypeScript」を用いた Reactアプリの開発環境を構築する手順 について解説します。

3.1 準備

ターミナル (PowerShell) を開いてプロジェクトフォルダを配置したいフォルダ (例えば C:\Users\xxxx\Documents\ など) に移動してください。いまから、このフォルダのなかに react-todo-app というプロジェクトフォルダを作成して、そこに「React (TypeScript+Tailwind CSS) の開発環境」を構築していきます。

注意

ドキュメントフォルダを OneDrive 同期している学生は、その影響のないフォルダ (c:\work など) で作業することを強く推奨します。OneDrive 同期フォルダで作業すると予期せぬトラブルの発生や、OneDrive容量を圧迫することがあります。

3.2 Viteによるプロジェクトフォルダの作成

ここではVite という「ウェブ開発用のビルドツール」を使って環境を構築していきます。Vite は「ビート」「ヴィート」のように読みます (稀に「バイト」や「ビーテ」のように読む人もいます)。

ターミナルから以下のコマンドを実行してください。

npx create-vite@latest react-todo2-app --template react-swc-ts

コマンドオプションの react-todo-appプロジェクトの名称 (=プロジェクトフォルダの名称) を指示しています。--template react-swc-ts では React と TypeScript と SWC を統合した開発環境テンプレート を使って環境構築することを指示しています。

create-react-app

2024年の前半ぐらいまでは npx create-react-app というコマンドでも同様に環境構築が可能でしたが、現在はアップデートされていないので、Vite を使うようにしてください。

なお、npx create-vite を初めて実行するときや、前回実行から時間が経っているときは、以下のように尋ねられるので、続行するために y を入力してください。

Need to install the following packages:
create-vite@8.0.2
Ok to proceed? (y)

次のように Use rolldown-vite (Experimental)? については No を、Install with npm and start now? については Yes を選択してください。

> npx
> create-vite react-todo-app --template react-swc-ts

│
◆  Use rolldown-vite (Experimental)?:
│  ○ Yes
│  ● No
│
◆  Install with npm and start now?
│  ● Yes / ○ No
│
◇  Scaffolding project in C:\Users\xxxx\Documents\react-todo-app...
│
◇  Installing dependencies with npm...

added 206 packages, and audited 207 packages in 25s

44 packages are looking for funding
  run `npm fund` for details
  
found 0 vulnerabilities
│
◇  Starting dev server...

> react-todo-app@0.0.0 dev
> vite


  VITE v7.1.10  ready in 1618 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

以上により、プロジェクトの雛形が作成され (=スキャフォールディング)、依存パッケージのインストール、アプリのビルド、そして開発サーバの起動までが自動的に完了します。

(プロンプト例)

ウェブアプリ開発環境構築に関する文脈で「スキャフォールディング」とは、どのような意味ですか。

最低限のコンテンツを含んだ「Reactアプリ」が、開発サーバで起動済みなので、ウェブブラウザからhttp://localhost:5173/にアクセスして確認してください。

img

画面中央下部の「Count is 0」のボタンをクリックすると、その表示がクリックのたびに「1」「2」「3」と更新されます。

このカウントアップは React の useState という主要な機能を利用して行なわれています。数値のカウントアップは 「Hello World」や「(組込み開発の)Lチカ」のような位置づけ になります。

動作が確認できたら、ターミナルから [Ctrl]+[C] を入力して、開発サーバを停止してください。

3.3 プロジェクトフォルダの構成確認

作成されたフォルダ (react-todo-app) に cd コマンドでに移動して VSCode を立ち上げてください。

cd react-todo-app
code .

VSCodeのエクスプローラから、フォルダ構成が次のようになっていることを確認してください。

img

プロジェクトフォルダに配置される各ファイルの概要は以下のようになります。

3.3.1 tsconfig.**.json

プロジェクトフォルダ内の tsconfig.jsontsconfig.app.jsontsconfig.node.json の3ファイルはいずれも TypeScript関連の設定ファイル となります。

「tsconfig.json」の内部から、「tsconfig.app.json」と「tsconfig.node.json」を参照する構成になっています。実際にVSCodeで tsconfig.json の内容を確認してください。

これらのファイルは、基本的に直接編集することはありません。

3.3.2 README.md

README.md」は、GitHubのリポジトリのトップページに表示される「README」を記述するファイルになります。マークダウン形式 (.md拡張子) で記述します。

特に、プロジェクトを「ポートフォリオに位置づける場合」には、アプリ概要 (機能一覧) やライセンス情報の他に 開発の経緯(なぜそのアプリを制作したのか)技術スタック (プロジェクトで使用している「フレームワーク」「ライブラリ」「ツール」など)、こだわりのポイント を記載するようにしてください。

3.3.3 .gitignore

.gitignore」は、Gitを使ったバージョン管理を「しない」ファイルやフォルダを設定するためのファイルになります。Git管理 (バージョン管理) をしない = GitHub にもアップされない ということになります。

.gitignore」の内容を確認すると、パッケージ本体を格納している node_modules フォルダのほか、DB接続情報やAPIをキーを格納するファイル ( env.local ) などが、Git管理から除外されるようになっています (=GitHubにアップにされないようになっています)。

3.3.4 eslint.config.js

eslint.config.js」はESLint という「コード品質チェックツール」に関する設定ファイルになります。例えば、ESLint を使うと 宣言されているが未使用の変数 があるときなどに「警告」や「エラー」として検出してくれます。

なお、「Lint」は (TypeScriptに限らず) プログラミング一般において「ソースコードを解析し、バグやスタイルの問題、疑わしい構造などを検出するプロセス」を指し、そのためのツールが Linter(リンター) と呼ばれています。C言語 や Python用の Linter もあります。ESLint は、特に ECMAScript (JavaScript / TypeScript) 用の標準Linterとして知られています。

この授業でのReact開発では、フォーマッターとしては「Prettier (プリティア)」、リンターとしては「ESLint (イーエスリント)」を使用していきます。両者の「役割」や「機能」の違いが気になる人は、生成AIで解決してください。

(プロンプト例)

React (TypeScript) 開発の際に、フォーマッターには「Prettier」を、リンターには「ESLint」を使用するように勧められました。両者の役割や機能の違いについて教えてください。

3.4 インストールパッケージ (ライブラリ) の確認

プロジェクトフォルダのなかの「package.json」の内容を確認してください。

第01回講義で解説したように、package.json は アプリの開発や実行にに必要なパッケージ (ライブラリ) の情報 や、開発に使用するコマンドをショートカットとして定義 する設定ファイルとなります。

package.json のなかの "dependencies""devDependencies" の項目を確認してください。TypeScript を用いて React開発するために 最低限必要となるパッケージ (ライブラリ) が記載されています。

ここに記載されたパッケージ (ライブラリ) は「node_modules」フォルダのなかにインストールされます (vite コマンドによって、既にインストールされています) 。「package-lock.json」については第01回講義で解説済みですが、詳しく知りたい人は生成AIで解決してください。

(プロンプト例)

Node.js環境における package.jsonpackage-lock.json の違いについて詳しく教えてください。

3.5 Reactアプリのビルドと動作確認

VSCode のターミナルから npm run dev コマンドを入力すると、開発モード で Reactアプリを起動することができます。dev は Development (開発) の意味です。

npm run dev

コマンドを実行すると、ターミナルに次のように表示がされ、ローカルホストの 5173 番ポート でアプリが立ち上がります。

PS C:\Users\xxxx\Documents\react-todo-app> npm run dev     

> react-todo-app@0.0.0 dev
> vite

  VITE v7.1.10  ready in 1107 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

ウェブブラウザでhttp://localhost:5173/にアクセスし、アプリが起動していることを確認してください。開発モードで実行中は ソースファイルを変更 すると、その内容が自動的にアプリに反映されます。

アプリの動作が確認ができたら「停止」してください (VSCodeのターミナルをアクティブにして [Ctrl]+[C] を押下してください)。


上記では「開発モード」を使ってアプリを実行しましたが、今度は プレビューモード (疑似本番モード) でアプリを実行してみたいと思います。

まずは、ターミナルに npm run build を入力して「ビルド」を実行します。

npm run build

プロジェクトフォルダのなかに distフォルダ が新たに追加されて、そのなかにビルドされたファイルが格納されます。

img

dist フォルダに出力されたアプリを実行するためには npm run preview コマンドを入力します。

npm run preview

今度はアプリが 4173番ポート で立ち上がるので、ウェブブラウザからhttp://localhost:4173/にアクセスして動作を確認してください。「開発モードでの実行」と「プレビューモードでの実行」の違いについては、ウェブ検索や生成AIの解説を参照してください。

(プロンプト例)

ビルドツールに Vite を使用して React 開発をしています。npm run dev コマンドでアプリを実行したときと、 npm run build してから npm run preview コマンドでアプリを実行したときの違いを教えてください。

プレビューモードでの動作を確認できたら、[Ctrl]+[C]アプリを停止 しておいてください。

3.6 不要なファイルや記述を削除

Reactについて要点を押さえて理解するために、プロジェクトフォルダから不要なファイルを削除し、いくつかのファイルについて不要な記述を削除してしていきます。

3.6.1 index.html の編集

index.html を次のように編集してください。<html> タグと <title> タグの部分を書き換えています。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>TodoApp</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

その他の箇所についても、リンターやフォーマッターによって 「タグの大文字小文字」や「引用符」が書き換わること がありますが、そこは気にする必要はありません (以降も同様)。

最後に「保存すること」を忘れないでください。

3.6.2 src/App.tsx の編集

src/App.tsx を次のように編集してください。

import { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const countUp = () => {
    const newCount = count + 1;
    setCount(newCount);
  };
  return (
    <div>
      <h1>TodoApp</h1>
      <button onClick={countUp}>count is {count}</button>
    </div>
  );
};

export default App;

拡張子 .tsx は、React用の JSX(XMLライクな拡張構文)および TypeScript に対応したプログラムが記述されていることを示しています。このプログラムの内容について後で解説するので、現時点では深く理解する必要はありません (まずは、開発環境の構築を優先して進めます)。

さきほどと同様に、最後に「保存すること」を忘れないでください。

3.6.3 ファイルとフォルダの削除

このあと Tailwind CSS を導入することを前提に CSS 関連のファイルとフォルダを削除します。

Reactのロゴが描かれたファイル (src/asset/react.svg) も不要なので、src/asset フォルダごと削除してください。

結果的に、以下のようになるようにしてください。

img

3.6.4 README.md の編集

README.md を次のように MIT License 書き換えてください。

第13行目Copyright (c) 2025 Kosen Taro については、適当に書き換えてください (名前の部分はハンドルネームでOKです)。

# TodoApp

React、TypeScript、Tailwind CSS を使用し、ローカルストレージでデータを永続化した「Todoアプリ」です。

## 開発履歴

- 2025年10月23日:プロジェクト開始

## ライセンス

MIT License

Copyright (c) 2025 Kosen Taro

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

3.7 動作確認 (CSSの剝ぎ取り)

次のコマンドを実行して、開発モードで動作確認します。

npm run dev

CSS関連の設定を削除したので、次のような シンプルなアプリ画面 (=CSSが適用されていない素のHTMLによる最低限のデザインとレイアウト構成) になっていることが確認できると思います。ただし、ボタンを押下したときのカウントアップ は引き続き機能することを確認してください。

img

動作が確認できたら、VSCode のターミナルから [Ctrl]+[C] を入力して、開発サーバを停止しておいてください。

3.8 Tailwind CSS の適用

この授業ではTailwind CSSという「CSSフレームワーク」を使用します (既に他のCSSフレームワークを使い慣れている人は、そちらを利用してもらっても問題ありません)。TailwindCSS は、あらかじめ用意されている 細かく分割されたデザインの部品 を組み合せてUIをスタイリングする点に特徴あります。

例えば、ボタン要素 に対して…

…といったデザイン部品の組み合わせを <button className="bg-blue-500 px-4 rounded"> のように与えてスタイリングします。

Tailwind CSS を用いることで CSSクラスの名前 を考えたり、スタイルの衝突を心配する必要がないので開発速度が上がり、デザインの「一貫性」や「保守性」 も保ちやすくなります。なお、本科目も後半で扱う Next.js は、標準で Tailwind CSS をサポートしており、その他、様々なプロダクトでも採用されています。

3.8.1 Tailwind 関連パッケージのインストール

次のコマンドを実行して「tailwindcss」「@tailwindcss/vite」「prettier」「prettier-plugin-tailwindcss」という Tailwind CSS 関連の4つのパッケージをインストールしてください。

なお、Tailwind CSS で設定されたスタイルは、ビルド時に通常のCSSに変換されるため、いずれのパッケージも「開発時のみ」に必要となります (本番環境での実行時には使用しません)。そのため -D オプションを付加して開発依存関係 (devDependencies) として扱います。

npm i -D tailwindcss @tailwindcss/vite
npm i -D prettier prettier-plugin-tailwindcss 

3.8.2 設定ファイルの追加と変更

プロジェクトのルート (トップレベル) に .prettierrc というファイルを新規作成し、以下の内容を記述してください。これは第01回講義でインストールするように指示した Prettier というフォーマッタ (VSCode) に関する拡張機能に関連する設定になります。

img
{
  "semi": true,
  "plugins": ["prettier-plugin-tailwindcss"]
}

これにより、Prettier が Tailwind CSS のユーティリティクラスを自動的に並び替えてくれるようになります。たとえば <div class="text-center bg-blue-500 p-4"> のようなクラス指定は、保存時に Tailwind の推奨順序 (レイアウト → 表示 → 色 → テキスト…のような体系) に沿って自動整列されるようになります。


次に、プロジェクトのルートの vite.config.ts という設定ファイルを以下のように変更してください。忘れずに保存もしてください。第03行目tailwindcss をインポートして、第07行目 でプラグインとして設定しています。

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import tailwindcss from "@tailwindcss/vite";

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
});

さらに、src/index.css (先ほど内容を空にしたファイル) に、次の内容を記述してください。忘れずに保存もしてください。

@import "tailwindcss";

なお、上記の src/index.css ファイルは、src/main.tsx第03行目 で読み込まれ、アプリ全体で Tailwind によるスタイリングが有効 になります。

各ファイルとも 保存されていることを再確認 してください。

この状態で npm run dev で動作確認すると、次のように標準HTMLによるデフォルトのスタイルを含めて、全てのスタイリング設定が剝がされた状態のアプリ画面になります。

img

ボタンとしての枠線が無くなって分かりづらいですが「count is 0」の文字を押下すると、問題なくカウントアップは機能することを確認してください。

3.8.3 TailWind によるスタイリング

ここから「Appコンポーネント」が記述されている src/App.tsxTailwind CSS によるスタイリング を適用していきます。ファイルの内容を、次のように書き換えてください。

import { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const countUp = () => {
    const newCount = count + 1;
    setCount(newCount);
  };
  return (
    <div className="mx-4 mt-10 max-w-2xl md:mx-auto">
      <h1 className="mb-4 text-2xl font-bold">TodoApp</h1>
      <button
        className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-700"
        onClick={countUp}
      >
        count is {count}
      </button>
    </div>
  );
};

export default App;

書き換えたのは 第10行目 から 第15行目 の範囲 (return しているJSX構文の内部) になります。

いずれも HTMLタグ (厳密にはJSXタグ) に className 属性を追加して、そこに Tailwindのクラス (mx-4font-bold など) を記述しています。HTMLタグでは class 属性にCSSクラスを記述しましたが、JSXタグ (.jsx.tsxファイルで利用可能な構文) では className 属性にクラスを記述します。

HTMLとCSS

HTML」と「CSS」の基礎は、情報3の「第11回講義」と「第12回講義」で学習済みです。

以下のように、CSSファイル main.css を作成し、そこに「CSSクラス」を独自に定義して <div class="container" > のように class 属性を使ってHTML要素のスタイリングをしました。

<!DOCTYPE html>
<html lang="ja" >
  ~~~ 略 ~~~
<body>
    <div class="container" >
      <h1>XXX's Curry Recipe</h1>
      ~~~ 略 ~~~
    </div>
</body>
</html>
.container { 
  max-width: 768px;
  width: 100%;
  margin: 0 auto;
} 

以上で Tailwind CSS の適用が完了しました。

npm run dev で動作確認してください。

ボタンにマウスカーソルを重ねると (ホバーすると) 色が変わることや、ウィンドウ幅を変えても左右 (X方向) の余白が適切に調整されること も確認してください。また、レスポンシブデザインになっていることも確認してください ([F12] でデベロッパツールでモバイル表示に切り替えてみてください)。

img

3.8.4 演習

開発モード (npm run dev) でアプリを実行しているときは ホットリロード が有効になります。つまり、コードを書き換えて保存すると、その結果が自動でウェブブラウザに反映されます

実際に開発モードでアプリを起動し、以下のように src/App.tsx のコードを書き替えて保存すると、それが どのように画面に反映されるか を確認してください。

参考: TailwindCSSの色指定の一覧

3.8.5 Tailwind スタイルの調べ方

className 属性を使って設定する各種スタイル (ユーティリティクラス) については、都度、解説していきますが、自分で調べたいときはTailwindの公式ページや生成AIを活用してください。

(プロンプト例)

TailwindCSS で、次のようにスタイルが適用されていました。各CSSクラスの意味について解説してください。
<div className="mx-4 md:mx-auto mt-10 max-w-2xl">

(プロンプト例)

TailwindCSS でボタンに「ドロップシャドウ」を適用したいです。どのようにすればよいですか。
<button className="bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded-md">ほげ</button>

4 ポート設定のカスタマイズ

Viteで構築したプロジェクトは、デフォルト設定では 開発モードnpm run dev で実行)が 5173番ポート で起動します。このポート番号は vite.config.ts から任意に設定ができます。後期中間以降に利用するNext.jsでは開発サーバが 3000番ポート で起動するので、それにあわせて、ここでも 3000番ポート で開発サーバが起動するように設定を変更してみます。

プロジェクトフォルダの直下にある vite.config.ts を、次のように変更してください。。

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc"
import tailwindcss from "@tailwindcss/vite";

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
  server: {
    port: 3000,
    strictPort: false,
    open: true,
  },
});

ターミナルから npm run dev を実行し 3000番ポート で開発サーバが起動することを確認してください。

PS C:\Users\xxxx\Documents\react-todo-app> npm run dev

> react-todo-app@0.0.0 dev
> vite


  VITE v5.4.9  ready in 1004 ms

  ➜  Local:   http://localhost:3000/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

また open: true の設定により ウェブブラウザも自動起動する ようになっています。さらに strictPort: false の設定により、3000番ポートが他のアプリで既に使われている場合、3001番ポートでの起動が試されます。同様に、3001がダメなら、3002、3003…と起動が試行されます。

5 ESLintのカスタマイズ

ESLint (=文法ミスやスタイルの不統一を指摘する静的解析ツール) に関連するカスタマイズについて簡単に説明しておきます。src/App.tsx を開いて、以下のように 第04行目const hoge = "Hoge"; というコードを追加してください。

import { useState } from "react";

const App = () => {
  const hoge = "Hoge"; // ■■■ 追記 ■■■
  const [count, setCount] = useState(0);
  const countUp = () => {
    const newCount = count + 1;
    setCount(newCount);
  };
  // 以下、略

すると、追記した const hoge = "Hoge" の部分で次のように「エラー」が発生します🤮。

‘hoge’ is assigned a value but never used. eslint@typescript-eslint/no-unused-vars
‘hoge’ が宣言されていますが、その値が読み取られることはありません。ts(6133)

img

ただし、この状態であっても 開発モード (npm run dev) で起動した開発サーバ では 問題なくページが表示 されます。これは、このエラーが「ESLintとしてのエラー」であって「実行時エラー (Runtime Error)」ではないためです。

一方で、ビルド(npm run build)を実行すると、以下のように明確なエラーとして処理が中断されてしまいます。当然、dist フォルダにもビルドしたファイルが出力されません。

PS C:\Users\xxxx\Documents\react-todo-app> npm run build

> react-todo-app@0.0.0 build
> tsc -b && vite build

src/App.tsx:4:9 - error TS6133: 'hoge' is declared but its value is never read.

4   const hoge = "Hoge"; // ■■■ 追記 ■■■
          ~~~~


Found 1 error.

このように 開発モードでの実行には問題がないものの、本番用にビルドすると失敗する ということは多々あります。そのため、VSCodeでエラー(赤色波線)になっている箇所 は決して放置せずに修正し、また定期的に npm run build を実行することをお勧めします。アプリが完成したと思っていたのに、大量のビルドエラーでデプロイに失敗すると精神的なダメージが大きいです。


・・・とはいえ「hogeが宣言されていますが、その値が読み取られることはありません」のような軽微なコード規約違反がエラー扱いになると、開発途中では非常に不便です(特に個人開発という場面では)。例えば npx create-next-app@latest コマンドで作成する「Next.jsの開発環境」では 変数の未使用は「エラー」ではなく「警告」という扱い になっています(警告レベルなのでビルドも可能です)。このように、Viteで構築されるデフォルト環境のESLintの設定は少し厳しいもの になっています。

ということで、「変数の未使用」が「エラー」ではなく「警告」の扱いになるようにESLintの設定をカスタマイズ していきます。

プロジェクトのルートの tsconfig.app.jsonnoUnusedLocalsnoUnusedParameters の設定 (第21行目と第22行目) を、以下のように true から false に変更してください。

{
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "target": "ES2022",
    "useDefineForClassFields": true,
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "types": ["vite/client"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "moduleDetection": "force",
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": false /* ESLintでチェックするため無効化 */,
    "noUnusedParameters": false /* ESLintでチェックするため無効化 */,
    "erasableSyntaxOnly": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true
  },
  "include": ["src"]
}

(プロンプト例)

tsconfig.jsonnoUnusedLocalsnoUnusedParameters は、どのような設定ですか。それぞれを true から false に変更すると、どのようにコンパイルの挙動が変わりますか。


次に、プロジェクトのルートの eslint.config.js に、以下のように rules: {...} のセクションを追加してください (第22行目 から 第35行目)。これにより、未使用のローカル変数や、未使用の関数パラメータ (仮引数) があっても、警告 (warning) の扱いになります。

import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
import { defineConfig, globalIgnores } from "eslint/config";

export default defineConfig([
  globalIgnores(["dist"]),
  {
    files: ["**/*.{ts,tsx}"],
    extends: [
      js.configs.recommended,
      tseslint.configs.recommended,
      reactHooks.configs["recommended-latest"],
      reactRefresh.configs.vite,
    ],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    }, 
    rules: {
      "no-unused-vars": "off",
      "@typescript-eslint/no-unused-vars": [
        "warn",
        {
          args: "after-used",
          vars: "all",
          ignoreRestSiblings: false,
          argsIgnorePattern: "^_",
          varsIgnorePattern: "^_",
          caughtErrorsIgnorePattern: "^_",
        },
      ],
    },
  },
]);

eslint.config.js を変更して保存するときには、少し時間がかかることがあります(全てのコードが再チェックされるため)。

以上の設定を済ませると src/App.tsxconst hoge = "Hoge"; のところが警告の扱い(赤色の波線から黄色の波線の表示)に変わっていることが確認できると思います。また npm run build を実行した際も、エラーで中断されることなくビルドが完了するはずです。

このようにLint関連のカスタマイズは eslint.config.jstsconfig.app.json でできることを知っておくと役立と思います。

注意 ESLint の動作が確認できたら App.tsx から const hoge = "Hoge"; を削除しておいてください。

5.0.1 演習 (宿題: 20分)

プロジェクトフォルダをリネームするか削除して、再度、環境構築に取り組み、大まかな手順を覚えてください。可能であれば、3回、4回と繰り返してください (慣れれば、5分程度で一連の環境構築が可能になります)。

6 Reactアプリケーションの実行順序の理解

Viteで構築したReactプロジェクトを 開発モード (npm run dev) で実行したときに、各ファイル (各コンポーネント) が読み込まれていく順番について解説します

まず一番初めにプロジェクトフォルダ直下にある index.html が読み込まれます。このように「プログラム実行の開始点 (起点) となるもの」を エントリーポイント といいます。

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>react-todo-app</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

次に、上記の 第11行目 に記述される <script type="module" src="/src/main.tsx"></script> によって /src/main.tsx が読み込まれます。

そして src/main.tsx第06行目document.getElementById("root") によって、index.html第10行目<div id="root"></div> の領域(idが root のdiv要素)に対して <App /> というコンポーネントがレンダリング (描画) されます。

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.tsx";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <App />
  </StrictMode>,
);

ここでレンダリングされる <App /> コンポーネントの実体は src/main.tsx第04行目 で読み込んでいる App.tsx となります。なお、<StrictMode>...<StrictMode> コンポーネントについては、いまは無視してください (気になる人は検索か生成AIで解決してください)。

そして、以下のような src/App.tsx によって、「TodoApp」という見出しや「count is 0」というボタンが画面に表示され、また 第05行目countUp 関数によってカウントアップが機能します。

import { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const countUp = () => {
    const newCount = count + 1;
    setCount(newCount);
  };
  return (
    <div className="mx-4 mt-10 max-w-2xl md:mx-auto">
      <h1 className="mb-4 text-2xl font-bold">TodoApp</h1>
      <button
        className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-700"
        onClick={countUp}
      >
        count is {count}
      </button>
    </div>
  );
};

export default App;

以上、整理すると開発モードでは、以下のような順番でファイルが読み込まれていくと考えてください (プログラムがネスト構造になっていると考えてください)。

  1. index.html が読み込まれ、その内部で …
  2. src/main.tsx が読み込まれ、その内部で …
  3. src/App.tsx (Appコンポーネント) が読み込まれる。

また、このあと Appコンポーネントの内部に、TodoListコンポーネント (src/TodoList.tsx) を配置して、さらに、その内部に TodoItemコンポーネント (src/TodoItem.tsx) を配置していきます。その場合は以下のようになります。

  1. index.html が読み込まれ、その内部で …
  2. src/main.tsx が読み込まれ、その内部で …
  3. src/App.tsx (Appコンポーネント) が読み込まれ、その内部で …
  4. src/TodoList.tsx (TodoListコンポーネント) が読み込まれ、その内部で …
  5. src/TodoItem.tsx (TodoItemコンポーネント) が読み込まれる。

6.1 ビルド (バンドル) 出力

ビルド (npm run build) を実行すると、最適化されたプロダクトが「distフォルダ」に出力されます。

このプロダクトは トランスパイル (TypeScript👉JavaScript)、最適化、バンドル などの処理によって ロジックとファイルが集約されたもので、index.html をエントリポイントとして assets/index-XXXX.jsassets/index-XXXX.css が読み込まれるシンプルな構成になります。

img

実際にビルドを実行して「distフォルダ」の各ファイルの内容について確認してみてください。

img

7 Reactアプリの開発

7.1 Reactにおけるコンポーネントとは

Reactアプリは、基本的に「コンポーネント」とよばる 機能とUIを持った画面構成の部品・パーツ を組み合わせてアプリを構築する点に特徴があります。コンポーネントとは、具体的には .tsx という拡張子を付けたファイルに記述した「JSX要素を戻り値とする関数」を指します。そして、JSX とは、JavaScript XML の略で、JavaScript (TypeScript) プログラムのなかに HTMLのようなタグ を記述できるようにした拡張構文を指します。

例えば「ウェルカムメッセージを表示するようなコンポーネント」をTodoアプリに組み込みたいときは、以下のように src/WelcomeMessage.tsx を作成します。コンポーネントを記述したファイルは、大文字からはじまるパスカルケースで名前を設定します。

import React from "react";

// 引数の型を定義
// Propsという名前で定義することが一般的です。
type Props = {
  name: string;
};

// WelcomeMessage という関数コンポーネントの定義
// 関数コンポーネントはパスカルケースで名前を設定します。
const WelcomeMessage = (props: Props) => {
  // いわゆる普通のロジックを記述する
  const currentTime = new Date();
  const greeting =
    currentTime.getHours() < 12 ? "おはようございます" : "こんにちは";

  //【重要!】JSX構文で描いた「JSX要素」を return で返す
  return (
    <div className="text-blue-700">
      {greeting}、{props.name}さん。
    </div>
  );
};

// 他のファイルで WelcomeMessage を import できるようにする
export default WelcomeMessage;

そして、src/App.tsx なかで以下のようにインポートして組み込みます。

import { useState } from "react";
import WelcomeMessage from "./WelcomeMessage"; // 追加

const App = () => {
  const [count, setCount] = useState(0);
  const countUp = () => {
    const newCount = count + 1;
    setCount(newCount);
  };
  return (
    <div className="mx-4 mt-10 max-w-2xl md:mx-auto">
      <h1 className="mb-4 text-2xl font-bold">TodoApp</h1>
      <WelcomeMessage name="寝屋川タヌキ" /> {/* 追加 */}
      <button
        className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-700"
        onClick={countUp}
      >
        count is {count}
      </button>
    </div>
  );
};

export default App;

実際に試してみてください。次のような結果が得られるはずです。

img

src/App.tsx では、第02行目 で「WelcomeMessageコンポーネント」を読込み、第13行目<WelcomeMessage name="寝屋川タヌキ" /> のように組み込んでいます。ここでは name という引数が、どのようにWelcomeMessageコンポーネントに渡されているか (対応関係) をよく確認してください。

なお、JSX要素のなかでは、コメントは {/* コメント */} のように記述します。

Reactの関数コンポーネントはパスカルケースで名前を設定すること

TypeScriptでは、通常、関数名には キャメルケース (小文字から開始) を使用します。ただし、Reactの関数コンポーネントは、パスカルケース (大文字から開始) を使用する必要があります (また、これにあわせてファイル名も パスカルケース (大文字から開始) にします)。この命名規則から外れていると、次のように JSX構文のなかでコンポーネントが適切に認識されません。注意してください。

img

7.1.1 演習

実際に「WelcomeMessageコンポーネント」を作成して、「Appコンポーネント」のなかに組み込んで組み込んでください。また、開発モードで実行結果を確認してください。

また、以下の要求を満たすようにプログラムを書き換えてください。

7.2 状態管理

Reactアプリでは アプリ画面上で変化する値状態(State) という「特別な仕組み」で管理します。この「状態」が更新されると、Reactが変更を検知し、関連するコンポーネントだけを自動的に再レンダリング (つまり再描画して値の変更を反映) します。

何らかの値 (例えば「現在のカウント」) を「状態」として扱い、その変更に応じて画面を再レンダリングするには、通常の変数として定義するのでは不十分であり、フックス (Hooks) という機能を利用して値を管理 (保持・更新) することが求められます。Reactには、状態管理のために様々な Hooks が用意されていますが、そのなかでも特に基本的な役割を果たすものが useState という フック (Hook) になります。

サンプルとして示したアプでは、カウント値が更新されたときに、それを即座に画面に自動反映させたいので、第04行目 のように useState を使って、カウント値を保持する変数 count と、それを更新するための専用関数 setCount を導入しています。なお、useState の引数には初期値を与えます (例えば、カウントを「7」から開始したいときは const [count, setCount] = useState(7); とします)。

そして、現在のカウント値を得たいときは count を参照し、更新したいときは setCount(newCount) を呼び出すことで、意図するようなカウント動作が実現されます。

import { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0); // ◀◀ 初期化(初期値を設定)
  const countUp = () => { 
    const newCount = count + 1;
    setCount(newCount); // ◀◀ 更新処理
  };
  return (
    <div className="mx-4 mt-10 max-w-2xl md:mx-auto">
      <h1 className="mb-4 text-2xl font-bold">TodoApp</h1>
      <button
        className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-700"
        onClick={countUp}
      >
        count is {count}
      </button>
    </div>
  );
};

export default App;

一方で、以下のように useState を使用せずに実装するとボタンを押下しても画面が更新されません (=カウント表示が変わらない)。実際に試してみてください。

const App = () => {
  let count = 0; // 通常の変数として定義
  const countUp = () => {
    count++; // 通常の処理で更新 count = count + 1; でも同じ
    console.log(`現在の count の値は ${count} です`); // 確認出力
  };
  return (
    <div className="mx-4 mt-10 max-w-2xl md:mx-auto">
      <h1 className="mb-4 text-2xl font-bold">TodoApp</h1>
      <button
        className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-700"
        onClick={countUp}
      >
        count is {count}
      </button>
    </div>
  );
};

export default App;

ただし、[F12] でデベロッパーツールを起動して、コンソールタブからログを確認してみると、画面上の「カウント値」が更新されないだけで、内部的には変数 count は正常に更新されていること が確認できます。

img

これらのことから「変更をトリガーに再レンダリングする必要ない値」については通常の変数として扱い、逆に「値の変更をトリガーに再レンダリングしたいもの」は useState を使って「状態」として扱う 必要があることが分かると思います。

7.3 練習

次のように、カウント値の表示部をボタンの外に移動し、ボタンを「1Up」と「10Up」の2個に増やすようにアプリを拡張してみます。

img

まずは、以下のようにプログラムを書き替え、実行結果とプログラムを照らし合わせてプログラムについて理解してください。

import { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const countUp1 = () => {
    const newCount = count + 1;
    setCount(newCount);
  };
  const countUp10 = () => {
    const newCount = count + 10;
    setCount(newCount);
  };
  return (
    <div className="mx-4 mt-10 max-w-2xl md:mx-auto">
      <h1 className="mb-4 text-2xl font-bold">TodoApp</h1>
      <div className="mb-2">
        <p>
          現在のカウント値は
          <span className="text-xl font-bold text-blue-500"> {count} </span>です
        </p>
      </div>
      <div className="flex flex-row gap-2">
        <button
          className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-700"
          onClick={countUp1}
        >
          1 Up
        </button>
        <button
          className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-700"
          onClick={countUp10}
        >
          10 Up
        </button>
      </div>
    </div>
  );
};

export default App;

このプログラムには冗長な記述が多いのでリファクタリングしていきます。関数 countUp1countUp10 は、以下のように省略形で (1行で) 書き換えが可能です。前回講義で学んだアロー関数形式の応用です。

const countUp1 = () => {
  const newCount = count + 1;
  setCount(newCount);
};
const countUp10 = () => {
  const newCount = count + 10;
  setCount(newCount);
};
const countUp1 = () => setCount(count + 1);
const countUp10 = () => setCount(count + 10);

実際に書き換えて、問題なく動作することが確認してください。

さらに、関数が引数を受け取るように変更して、onClick での呼び出しを onClick={() => countUpN(1)}onClick={() => countUpN(10)} とすると、さらにスマートに記述ができます (全体のコードはこちらを参照)。

const countUpN = (n: number) => setCount(count + n);

また、関数 countUpN を削除してこちらのように onClick直接処理を記述するような実装 もあります。

7.3.1 演習

次のように、カウントをリセットするボタンを実装してください。

img

8 Todoアプリの実装

8.1 準備: ライブラリのインストール

個々の Todo に ID を設定するため UUID (Universally Unique Identifier) v4 を生成可能なuuidというライブラリをインストールします。また、日時の管理を簡単にするためにdayjsdate-fnsというライブラリをインストールします。

VSCodeのターミナルから以下のコマンドを実行してください。

npm i uuid dayjs date-fns

また、uuid ライブラリは (TypeScriptではなく) JavaScript で書かれており TypeScriptに必要な型定義を含んでいない ため、それを補う型定義 (@types/uuid) を追加でインストールします。なお、この型定義ファイルは 開発だけに必要 (実行時は不要) なので -D オプションを付けてインストールします。

npm i -D @types/uuid

アイコン用にFont Awesomeを使用したいので、型定義と共にインストールします。Font Awesome \(\to\)

npm i @fortawesome/react-fontawesome @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons
npm i -D @types/react-fontawesome

Tailwind 関連のユーティリティクラス (font-boldpx-4 など) を動的に結合する際に、スタイリングの衝突を解決してくれるtailwind-mergeをインストールします。開発時のみ必要なので -D オプションを付けてインストールします。

npm i -D tailwind-merge

8.2 準備: 初期データの作成

Todoリストの「初期データ」を準備します。まずは、このTodoアプリで共通使用する「」を定義しておくためのファイルを src/types.ts を作成してください。

作成したファイルに以下のように Todo型 を定義してください。また 定義した型が、他のファイルで import できるように export を付けてください。なお、このファイルは、単に型定義をしているだけのファイルなので拡張子は .tsx ではなく、.ts にします。

export type Todo = {
  id: string;
  name: string;
  isDone: boolean;
  priority: number;
  deadline: Date | null; // 注意
};

ここでの注意してほしいのは 第06行目 で、プロパティ deadline の型を「Date」もしくは「null」に設定している点です (前回講義の解説を参照)。特に「期限を設定しないTood」については deadlinenull として扱うものとします。

次に、Todoリストの初期データを格納するファイルとして initTodos.ts を作成し、以下のように初期値 initTodos を設定してください。この initTodo他のファイルから参照するので忘れずに export キーワードを付与してください。データの内容は自由に変更して問題ありませんが、このあとの展開の関係で 3個以上を設定 してください。

なお、第06行目uuid() は UUID v4 の文字列 (string型) を ランダム に生成する関数になります (第02行目 では v4 という関数を uuid という名前でインポートしています)。

import type { Todo } from "./types";
import { v4 as uuid } from "uuid"; // v4 を uuid という名前でインポート

export const initTodos: Todo[] = [
  {
    id: uuid(), // UUID v4 を生成してIDにセット
    name: "解析2の宿題",
    isDone: false,
    priority: 2,
    deadline: new Date(2024, 10, 2, 17, 30),
  },
  {
    id: uuid(),
    name: "TypeScriptの勉強 (復習)",
    isDone: true,
    priority: 3,
    deadline: null, // このTodoには期限を設定しない
  },
  {
    id: uuid(),
    name: "基礎物理学3の宿題",
    isDone: false,
    priority: 1,
    deadline: new Date(2024, 10, 11),
  },
];

8.3 TodoListコンポーネントの作成

Todoリストを表示するための「TodoListコンポーネント」を作成します。src/TodoList.tsx を作成して、以下の内容を記述してください。ここでは、練習のためにコピペではなくコードを打ち込んでください。前回講義で紹介しているように ES7+React/Redux/Reac1t-Native snippets をVSCodeの拡張機能としてインストールしていると、次の図のように rafce と打ち込むだけで アロー関数形式のReactコンポーネントのテンプレート が得られます。

img

ここで定義する TodoList は、Reactの関数コンポーネントなので必ずパスカルケースとしてください。また、同様に、ファイルの名前もパスカルケースとしてください。

import React from "react";
import type { Todo } from "./types";

type Props = {
  todos: Todo[];
};

const TodoList = (props: Props) => {
  const todos = props.todos;
  return (
    <div className="flex flex-col gap-y-1">
      <div>
        {todos[0].name} 優先度: {todos[0].priority}
      </div>
      <div>
        {todos[1].name} 優先度: {todos[1].priority}
      </div>
      <div>
        {todos[2].name} 優先度: {todos[2].priority}
      </div>
    </div>
  );
};

export default TodoList;

このコンポーネントは、Todo型の配列 todosProps (プロップス) として受け取り、先頭から3件分のTodoの「名前」を「優先度」を表示しています。なお、Reactの文脈において Props とは Properties の略称で、コンポーネントが受け取る「引数」のようなもの と考えてください。

なお、ここでの 第12行目 から 第20行目 のコードは酷いものですが、これは後で書き換えします。

8.4 Appコンポーネントの編集

src/App.tsx を次のように編集してください。第04行目 ではさきほど作成したウェルカムメッセージのコンポーネント (src/WelcomeMessage.tsx) をインポートしています。同様に 第05行目 では、Todoリストのコンポーネント(src/TodoList.tsx) をインポートしています。

また、第03行目では、Todoリストの初期データとして initTodos をインポートしています。なお、インポートする際に 波括弧 { } をつけてインポートしているケースと、そうでないケースがあること に気づくと思います。これらの理由については生成AIで解決してください。

(プロンプト例)

Node.js 環境で TypeScript の勉強しています。サンプルコードを見ていると、ライブラリから関数や変数をインポートする際に、波括弧 {} を付けているケースと、そうではないケースがあることに気づきました。違いについて教えてください。

import { useState } from "react";
import type { Todo } from "./types";
import { initTodos } from "./initTodos";
import WelcomeMessage from "./WelcomeMessage";
import TodoList from "./TodoList";

const App = () => {
  const [todos, setTodos] = useState<Todo[]>(initTodos);
  const uncompletedCount = initTodos.filter(
    (todo: Todo) => !todo.isDone
  ).length; // 未完了タスクの数え上げ
  console.log(JSON.stringify(todos, null, 2));

  return (
    <div className="mx-4 mt-10 max-w-2xl md:mx-auto">
      <h1 className="mb-4 text-2xl font-bold">TodoApp</h1>
      <div className="mb-4">
        <WelcomeMessage
          name="寝屋川タヌキ"
          uncompletedCount={uncompletedCount}
        />
      </div>
      <TodoList todos={todos} />
    </div>
  );
};

export default App;

編集後、保存してから 開発モード でアプリを実行してください (npm run dev)。次のような実行結果が得られると思います。さらに、ブラウザで [F12] を押下して デベロッパーツール を起動してコンソール出力 (第12行目 で行われている initTodos の出力) も確認してください。

img

src/App.tsxsrc/TodoList.tsx について、生成AIなどを活用して読解・理解をしてください。特に、しっかりと理解してほしい箇所は次のとおりです。

8.5 TodoListコンポーネントのブラッシュアップ

現状の src/TodoList.tsx は、todos の要素数に関わらず 固定で「3件」のTodoしか表示すること ができません。しかも、todos の要素数が 2個以下のときは実行時エラー が生じます (Todoが1個も表示されなくなります)。

そこで、前回講義で学んだ mapメソッド を利用して、todos の要素数にあわせて表示するように変えたいと思います。この mapメソッドを使うテクニックは Reactでは定番中の定番処理 となるので確実に読解かつ記述できるようにしてください。

import React from "react";
import type { Todo } from "./types";

type Props = {
  todos: Todo[];
};

const TodoList = (props: Props) => {
  const todos = props.todos;
  return (
    <div className="flex flex-col gap-y-1">
      <div>
        {todos[0].name} 優先度: {todos[0].priority}
      </div>
      <div>
        {todos[1].name} 優先度: {todos[1].priority}
      </div>
      <div>
        {todos[2].name} 優先度: {todos[2].priority}
      </div>
    </div>
  );
};

export default TodoList;

上記のプログラムを次のように書き換えます。前回講義のオブジェクト型の配列に対するmapの適用を参考にしながら、この書き換えを理解してください。

import React from "react";
import type { Todo } from "./types";

type Props = {
  todos: Todo[];
};

const TodoList = (props: Props) => {
  const todos = props.todos;

  if (todos.length === 0) {
    return (
      <div className="text-red-500">
        現在、登録されているタスクはありません。
      </div>
    );
  }

  return (
    <div className="space-y-1">
      {todos.map((todo) => (
        <div key={todo.id}>
          {todo.name} 優先度: {todo.priority}
        </div>
      ))}
    </div>
  );
};

export default TodoList;

書き換え後のプログラムでは、todos空配列のとき (todos.length === 0 のとき) には、Todoリストの代わりに「現在、登録されているタスクはありません。」というメッセージを表示するようにしています。

また、map の戻り値となる一番外側のJSX要素 (この例では <div> 要素) については、第22行目 のように 必ず key 属性を設定してくださいkey の値は ユニークな値 (配列内の他要素と重複のない一意な値) であれば何であっても問題ありません。例えば、Todoの「名前 (name)」が絶対に重複しないのであれば <div key={todo.name}> としても問題ありません。

key 属性を設定していないと、実行時に Each child in a list should have a unique "key" prop という警告が表示されます。また、再レンダリングに際して意図した動作をしなくなる可能性があります。詳しい原理は生成AIで解決してください。

(プロンプト例)

Reactを使ったウェブアプリを開発しています。配列のmapメソッドを使用してJSXを構成する際、key属性が要求されるのは何故ですか。Reactは何のためにkey属性を必要としますか。

8.5.1 演習

前回講義で学んだように、Todoの並び替えは「配列のsortメソッド」を利用して行なうことができます。例えば、優先度を昇順 (1👉2👉3の順) にしたいときは、第09行目const todos = props.todos; を以下のように書き換えます。

const todos = [...props.todos].sort((a, b) => a.priority - b.priority);

前回講義の複数のソートキーを使ったオブジェクト配列の並び替えを参考にして、第1ソートキーisDone (未完了を先に表示)、第2ソートキーdeadline として並び替えたTodoリストが表示されるようにしてください。

9 演習 (宿題: 120分~)

主に src/TodoList.tsxJSX (HTML)Tailwind CSS に編集を加えてTodoリストコンポーネントの表示を 自分流にカスタイマイズ してみてください。これは、UI/UXの設計練習も兼ねています。また、後期前半の大課題に関連する内容にもなってきます。

なお、現時点では「チェックボックス」や「ボタン」などの要素追加や、それに対するスタイリングは不要です (これらの内容は次回講義で扱います)。

(Todoリストのカスタマイズの例)

img

取組みに際しては、生成AIを賢く活用してください (コード生成ツールとして使うのではなく、内容の理解のためのツールとして使用してください)。

(プロンプト例)・再掲

TailwindCSS で、次のようにスタイルが適用されていました。各CSSクラスの意味について解説してください。
<div className="mx-4 md:mx-auto mt-10 max-w-2xl">

(プロンプト例)・再掲

TailwindCSS でボタンに「ドロップシャドウ」を適用したいです。どのようにすればよいですか。
<button className="bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded-md">ほげ</button>