1 連絡1
- 第35回高専プロコン(2024年
奈良大会)YouTube 10/19-20
- 全国の高専生はどんなアプリやソフトを開発しているの? …リンク先の「予選資料」から概要が確認できます。
2 連絡2
本科目は学修単位科目です。1回の授業あたり、皆さんが「4時間相当の授業時間外学習」をすることを前提 としたボリュームと展開速度となっています。1週間の間隔をあけての90分だけの取り組みで理解・習得できる内容ではないので注意してください。
初回授業から後期中間試験ぐらいまでの授業の流れ (予定)
- モダンTypeScript基礎学習のための環境構築 済
- TypeScript基礎学習 済
- React / Next.js 開発に関連する文法や機能だけを集中的に学びました。
- Reactを使ったTodoアプリのための環境構築 ← 今回の授業 (前半)
- Todoアプリ開発(Reactによるフロントエンド開発)のチュートリアル ← 今回の授業 (後半)、次回授業
- Todoアプリのカスタマイズや作り込み → 後期前半の大課題 (宿題)
今回講義の 後半の内容 は、前回講義で宿題として示した【React入門】完全初心者OK!1から簡単なTodoアプリを作ってReactの1歩を踏み出してみよう ~Reactチュートリアル~の視聴を前提とした内容です。
- この講義でも、上記動画とほぼ同じ機能を持ったTodoアプリを作成します。ただし、最終的に仕上がるアプリは同じであっても、本授業での開発環境やコードスタイル
(そもそも動画では TypeScript ではなく
JavaScript で作成している) などが違う点に注意してください。
- Todoアプリのサンプル (ギリギリ合格水準のレベル、点数で言えば60点😨)
- Todoアプリのサンプル (ここまでつくれたら素晴らしい!90点超🎉)
- 同じような機能を持ったアプリを開発する場合でも、そのアプローチが無数にあることを意識して学ぶと得られるものが多いです。
3 React開発環境の構築
Viteというビルドツールを使用して「Tailwind CSS」と「TypeScript」を用いた Reactアプリの開発環境を構築する手順 について解説します。
3.1 準備
ターミナル (PowerShell)
を開いてプロジェクトフォルダを配置したいフォルダ (例えば
C:\Users\xxxx\Documents\ など)
に移動してください。いまから、このフォルダのなかに
react-todo-app
というプロジェクトフォルダを作成して、そこに「React
(TypeScript+Tailwind CSS)
の開発環境」を構築していきます。
3.2 Viteによるプロジェクトフォルダの作成
ここでは、Vite という「ウェブ開発用のビルドツール」を使って環境を構築していきます。
Vite を使用する方法以外にも
npx create-react-app
ターミナル (PowerShell) 以下のコマンドを実行してください。
npm create vite@latest react-todo-app -- --template react-ts
コマンドオプションの react-todo-app は
プロジェクトの名称 (=プロジェクトフォルダの名称)
を指示しています。また -- は後続オプションを (npm
ではなく) Vite
に渡すことを意味し、--template react-ts では React と TypeScript
を統合した開発環境テンプレート
を使って環境構築することを指示しています。
なお、npm create vite@latest
を初めて実行するときや、前回実行から時間が経っているときは、以下のように尋ねられるので、続行するために
y を入力してください。
Need to install the following packages:
create-vite@5.5.3
Ok to proceed? (y)
補足
コマンドオプションに --template react-ts
を付けているので表示されないと思いますが、もし「Select a
framework」と聞かれたら「React」、「Select a
variant」と聞かれたら「TypeScript」を選択してください。
次のように応答が返ってくればコマンドオプションの
react-todo-app
は作成するプロジェクトの名称
(=プロジェクトフォルダの名称)、 -- は後続オプションを
npm ではなく Vite
に渡すことを意味するもので、--template react-ts
によって React と TypeScript を統合した開発環境
(テンプレート) を指示します。成功です。
Scaffolding project in C:\Users\xxxx\Documents\react-todo-app...
Done. Now run:
cd react-todo-app
npm install
npm run dev
3.3 プロジェクトフォルダの構成確認
作成されたフォルダ (react-todo-app) に
cd コマンドでに移動して VSCode
を立ち上げてください。
cd react-todo-app
code .
VSCodeのエクスプローラから、フォルダ構成が次のようになっていることを確認してください。
プロジェクトフォルダに配置される各ファイルの概要は以下のようになります。
3.3.1 tsconfig.**.json
プロジェクトフォルダ内の「tsconfig.json」「tsconfig.app.json」「tsconfig.node.json」の3ファイルはいずれも TypeScript関連の設定ファイル となります。「tsconfig.json」から「tsconfig.app.json」と「tsconfig.node.json」を参照する構成になっています (実際にVSCodeで「tsconfig.json」の内容を確認してください)。
- tsconfig.app.json は、これから自分で開発するアプリのソースコード に適用される「TypeScript設定」になります。
- tsconfig.node.json は、Node.js 環境で実行される範囲(例えばビルドスクリプトなど) に適用される「TypeScript設定」になります。
3.3.2 README.md
「README.md」は、GitHubのリポジトリのトップページに表示される「README」を記述するファイルになります。マークダウン形式
(.md拡張子) で記述します。
特に、プロジェクトを「ポートフォリオに位置づける場合」には、アプリ概要 (機能一覧) やライセンス情報の他に 開発の経緯(なぜそのアプリを制作したのか)、技術スタック (プロジェクトで使用している「フレームワーク」「ライブラリ」「ツール」など)、こだわりのポイント を記載するようにしてください。
3.3.3 .gitignore
「.gitignore」は、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」を使用するように勧められました。両者の役割や機能の違いについて教えてください。
- 参考: ESLint入門2023
導入・設定方法・Prettierとの違い解説@YouTube
- Vite
による環境構築は、ESLintの導入と初期設定を含むので動画内で解説されているような
npm init @eslint/configの実行は不要です。
- Vite
による環境構築は、ESLintの導入と初期設定を含むので動画内で解説されているような
3.4 パッケージ (ライブラリ) のインストール
プロジェクトフォルダのなかの「package.json」の内容を確認してください。
第01回講義で解説したように、package.json は アプリの開発や実行にに必要なパッケージ (ライブラリ) の情報 や、開発に使用するコマンドをショートカットとして定義 する設定ファイルとなります。
package.json のなかの "dependencies" と
"devDependencies"
の項目を確認してください。TypeScript を用いて React開発するために
最低限必要となるパッケージ (ライブラリ)
が記載されています。
ただし、現時点では、まだパッケージ (ライブラリ) の実体はインストール されていません (プロジェクトフォルダのなかに「node_modules」フォルダが存在しません)。いまから package.json に記載されたパッケージをプロジェクトフォルダにローカルインストールしていきます。
VSCode上から [Ctrl]+[J]
のショートカットでターミナルを開いて npm install
または npm i
を実行してください。これにより、package.json
に基づきパッケージがインストールされます。
npm i
ファイルのダウンロードなどがあるので、この処理は少し時間がかかります。
完了するとプロジェクトフォルダのなかに node_modulesフォルダ と package-lock.json が追加されます。「package-lock.json」については第01回講義で解説済みですが、詳しく知りたい人は生成AIで解決してください。
(プロンプト例)
Node.js環境における
package.jsonとpackage-lock.jsonの違いについて詳しく教えてください。
3.5 Reactアプリのビルドと動作確認
ここまでの作業で、Vite によって作成されたアプリ (のひな形)
を動作確認するための準備が整いました。ターミナルに
npm run dev
コマンドを入力して、開発モード で
Reactアプリを動かしてみてください。なお、npm run dev
によって実際に実行されるコマンドは「package.json」の "scripts" の項目に記載されているように
vite となります。
npm run dev
ターミナルに次のように表示がされ、ローカルホストの 5173 番ポート でアプリが立ち上がります。
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 420 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
ウェブブラウザでhttp://localhost:5173/にアクセスしてください。次のように表示されるはずです。また、画面内の「count is 0」のボタンを押下して 「数値」がカウントアップ することを確認してください。
このカウントアップは React の useState
という主要な機能を利用して行なわれています。数値のカウントアップは
「Hello
World」や「(組込み開発の)Lチカ」のような位置づけ
になります。
アプリの動作確認ができたら「停止」してください
(VSCodeのターミナルをアクティブにして [Ctrl]+[C]
を押下してください)。
上記では「開発モード」を使ってアプリを実行しましたが、今度は
プレビューモード (疑似本番モード)
でアプリを実行してみたいと思います。まずは、ターミナルに
npm run build
を入力して「ビルド」を行ないます。プロジェクトフォルダのなかに
distフォルダ
が新たに追加されて、そのなかにビルドされたファイルが格納されます。
npm run build
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>第02行目 の
<html lang="en">を<html lang="ja">に書き換えました。第07行目 の
<title>Vite + React + TS</title>を<title>TodoApp</title>に書き換えました。
その他の箇所についても、リンターやフォーマッターによって「タグの大文字小文字」や「引用符」が書き換わることがありますが、そこは気にする必要はありません (以降も同様)。
最後に「保存すること」を忘れないでください。
3.6.2 src/App.tsx の編集
src/App.tsx を次のように編集してください。拡張子
.tsx は、React用の
JSX(XMLライクな拡張構文)および TypeScript
に対応したプログラムが記述されていることを示しています。このプログラムの内容について後で解説するので、現時点では深く理解する必要はありません
(第12行目 でボタンをクリックすすると
onClick={countUp}
によって、第05行目でアロー関数形式で定義している
countUp
が呼び出されるのかな🤔…ぐらいの簡単な理解で十分です)。
まずは、開発環境の構築を優先して進めます。
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;さきほどと同様に、最後に「保存すること」を忘れないでください。
3.6.3 ファイルとフォルダの削除
このあと Tailwind CSS を導入することを前提に CSS 関連のファイルとフォルダを削除します。
src/App.cssを削除してください。index.cssの内容を全削除してください (ファイル自体は残すようにしてください)。
Reactのロゴが描かれたファイル
(src/asset/react.svg)
も不要なので、src/asset
フォルダごと削除してください。
3.6.4 README.md の編集
README.md を次のように書き換えてください。
第13行目 の
Copyright (c) 2024 Kosen Taro
については、適当に書き換えてください
(名前の部分はハンドルネームでOKです)。
# TodoApp
React、TypeScript、Tailwind CSS を使用し、ローカルストレージでデータを永続化した「Todoアプリ」です。
## 開発履歴
- 2024年10月24日:プロジェクト開始
## ライセンス
MIT License
Copyright (c) 2024 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関連の設定を削除したので、次のようなシンプルなアプリ画面になっていることが確認できると思います。ただし、ボタンを押下したときのカウントアップ は引き続き機能することを確認してください。
動作確認ができたら、停止しておいてください。
3.8 Tailwind CSS の適用
この授業ではTailwind CSSという CSSフレームワーク を標準的に使用します (既に他のCSSフレームワークを使い慣れている人は、そちらを利用してもらっても問題ありません)。TailwindCSS は、あらかじめ用意されている 細かく分割されたデザインの部品 を組み合せてUIをスタイリングする点に特徴あります。
例えば「青い背景色
bg-blue-500」「4単位量の左右余白
px-4」「四隅を丸める
rounded」といったデザイン部品の組み合わせを <button className="bg-blue-500 px-4 rounded">
のように与えてスタイリングします。CSSクラスの名前を考えたり、スタイルの衝突を心配する必要がないので開発速度がアップし、デザインの一貫性や保守性
も保ちやすくなります。なお、Next.js については、標準で Tailwind
CSS をサポートしており、様々なプロダクトで採用されています。
次のコマンドを実行して「tailwindcss」「postcs」「autoprefixer」という3つのパッケージをインストールしてください。Tailwind
CSS
で設定されたスタイルは、ビルド時に通常のCSSに変換されるため、いずれのパッケージも「開発時のみ」に必要となります(本番環境での実行時には使用しません)。そのため
-D
オプションを付加して開発依存関係としてインストールしています。
3.8.1 パッケージのインストール
次のコマンドを実行して「tailwindcss」「postcs」「autoprefixer」という3つのパッケージをインストールしてください。Tailwind
CSS
で設定されたスタイルは、ビルド時に通常のCSSに変換されるため、いずれのパッケージも「開発時のみ」に必要となります
(本番環境での実行時には使用しません)。そのため -D
オプションを付加して開発依存関係として扱います。
npm i -D tailwindcss postcss autoprefixer
3.8.2 設定ファイルの追加
プロジェクトに Tailwind CSS を適用するために設定ファイルを追加して、既存のファイルを書き換えます。
まず、プロジェクトフォルダ直下 ( src
の内部ではないので注意! ) に
tailwind.config.js
を新規作成して、次の内容を貼付けて保存してください。
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};同様に postcss.config.js
を新規作成して、次の内容を貼付けて保存してください。
src/index.css (空ファイルだったもの)
に、次の内容を記述してください。以下、警告マークがでますが、一旦、無視してください。
なお、上記の src/index.css
ファイルは、src/main.tsx の 第04行目
で読み込まれ、アプリ全体で Tailwind
によるスタイリングが有効 になります。
各ファイルとも保存されていることを再確認してください。
この状態で npm run dev
で動作確認すると、デフォルトのスタイルを含めて、全ての CSS
が剝がされた状態のアプリ画面になります。境界線が無くなって分かりづらいですが「count
is
0」の文字を押下すると、カウントアップは問題なく機能します。
3.8.3 TailWind によるスタイリング
Appコンポーネントが記述されている src/App.tsx に
Tailwind 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 md:mx-auto mt-10 max-w-2xl">
<h1 className="font-bold text-2xl mb-4">TodoApp</h1>
<button
className="bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded-md"
onClick={countUp}
>
count is {count}
</button>
</div>
);
};
export default App;書き換えたのは 第10行目 から
第15行目 の範囲 (return
しているJSX構文の内部) になります。
いずれも HTMLタグ
(厳密にはJSXタグ) に className
属性を追加して、そこに Tailwindのクラス
(mx-4 や font-bold など)
を記述しています。HTMLタグでは class
属性にCSSクラスを記述しましたが、JSXタグ
(.jsx や .tsxファイルで利用可能な構文)
では className 属性にクラスを記述します。
HTMLとCSS
「HTML」と「CSS」の基礎は、情報3の「第11回講義」と「第12回講義」で学習済みです。
以下のように、CSSファイル main.css
を作成し、そこに「CSSクラス」を独自に定義して
<div class="container" > のように
class
属性を使ってHTML要素のスタイリングをしました。
以上で Tailwind CSS の適用が完了しました。
npm run dev で動作確認してください。
ボタンにマウスカーソルを重ねると (ホバーすると) 色が変わることや、ウィンドウ幅を変えても左右 (X方向) の余白が適切に調整されること も確認してください。また、レスポンシブデザインになっていることも確認してください (デベロッパツールでモバイル表示に切り替えてみてください)。
3.8.4 演習
開発モード (npm run dev)
でアプリを実行しているときは ホットリロード
が有効になります。つまり、コードを書き換えて保存すると、その結果が自動でウェブブラウザに反映されます。
実際に開発モードでアプリを起動し、以下のように
src/App.tsx のコードを書き替えて保存すると、それが
どのように画面に反映されるか
を確認してください。
<h1>タグにtext-violet-600クラスを追加してください。- つまり
<h1 className="font-bold text-2xl mb-4 text-violet-600">TodoApp</h1>のようにしてください。
- つまり
<button>タグのクラスをrounded-mdからrounded-fullに変更してください。また、rounded-xsに変更してください。<button>タグのクラスをpy-2からpy-1に変更してください。また、py-0.5に変更してください。<button>タグにcursor-not-allowedを追加してください。
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";
export default defineConfig({
plugins: [react()],
server: {
port: 3000, // デフォルトのポートを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);
};
return (
<div className="mx-4 md:mx-auto mt-10 max-w-2xl">
<h1 className="font-bold text-2xl mb-4">TodoApp</h1>
<button
className="bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded-md"
onClick={countUp}
>
count is {count}
</button>
</div>
);
};
export default App;すると、追記した const hoge = "Hoge"
の部分で次のように「エラー」が発生します🤮。
‘hoge’ is assigned a value but never used. eslint@typescript-eslint/no-unused-vars
‘hoge’ が宣言されていますが、その値が読み取られることはありません。ts(6133)
ただし、この状態であっても 開発モード
(npm run dev) で起動した開発サーバ では
問題なくページが表示
されます。これは、このエラーが「ESLintとしてのエラー」であって「実行時エラー
(Runtime Error)」ではないためです。
一方で、ビルド(npm run build)を実行すると、明確なエラーとして処理が中断されてしまいます。当然、dist
フォルダにもビルドしたファイルが出力されません。
このように
開発モードでの実行には問題がないものの、本番用にビルドすると失敗する
ということは多々あります。そのため、VSCodeでエラー(赤色波線)になっている箇所は放置せずに修正し、また定期的に
npm run build
を実行することをお勧めします。アプリが完成したと思っていたのに、大量のビルドエラーでデプロイに失敗すると精神的なダメージが大きいです。
・・・とはいえ「hogeが宣言されていますが、その値が読み取られることはありません」のような軽微なコード規約違反がエラー扱いになると、開発途中では非常に不便です(特に個人開発という場面では)。例えば
npx create-next-app@latest
コマンドで作成する「Next.jsの開発環境」では 変数の未使用は「エラー」ではなく「警告」という扱い
になっています(警告レベルなのでビルドも可能です)。このように、Viteで構築されるデフォルト環境のESLintの設定は少し厳しいもの
になっています。
ということで、「変数の未使用」が「エラー」ではなく「警告」の扱いになるようにESLintの設定をカスタマイズ していきます。
まず tsconfig.app.json
について、次のように変更します
(tsconfig.node.json
ではないので注意してください)。"noUnusedLocals" と
"noUnusedParameters" の項目を変更してください。
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": false, /* 変更 */
"noUnusedParameters": false, /* 変更 */
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}つづいて eslint.config.js
に、'@typescript-eslint/no-unused-vars': 'warn',
の設定を追加してください。追加時には、オブジェクトリテラルのフォーマットに従い、末尾にカンマを付けること
を忘れないようにしてください。
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'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'@typescript-eslint/no-unused-vars': 'warn', // 追加
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)eslint.config.js を変更して保存するときには、少し時間がかかることがあります(おそらく全てのコードが再チェックされるためだと思います)。
以上の設定を済ませると src/App.tsx の
const hoge = "Hoge";
のところが警告の扱い(赤色の波線から黄色の波線の表示)に変わっていることが確認できると思います。また
npm run build
を実行した際も、エラーで中断されることなくビルドが完了すると思います。
このようにLint関連のカスタマイズは
eslint.config.js と tsconfig.app.json
でできることを知っておくと役立と思います。
5.1 Tailwind CSS 関連のLint
Tailwindを使用する場合、その特性上、次のようにJSXタグの
className
属性に多数のユーティリティクラスを列挙することになります。
<h1 className="font-bold text-2xl mb-4">TodoApp</h1>リンターであるESLintの設定 と フォーマッターであるPrettierの設定 をカスタマイズすることで、これらのクラスについて 自動で並び替えすること が可能になります(コードの「可読性」と「一貫性」が向上します)。
まず、VSCodeの拡張機能としての Prettier
(識別子:esbenp.prettier-vscode) とESLint
(識別子:dbaeumer.vscode-eslint)
はあらかじめインストールされているものとします。
そのうえで、次のように「prettier」と「eslint-plugin-tailwindcss」という開発用のパッケージをインストールします。
npm i -D prettier eslint-plugin-tailwindcss
つづいて eslint.config.js
を次のように変更してください。
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 tailwind from "eslint-plugin-tailwindcss"; // 追加
export default tseslint.config(
{ ignores: ["dist"] },
{
extends: [
js.configs.recommended,
...tseslint.configs.recommended,
...tailwind.configs["flat/recommended"], // 追加
],
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
"@typescript-eslint/no-unused-vars": "warn",
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
}
);さらに、プロジェクトフォルダの直下に
.vscode フォルダを作成して、そのなかに
settings.json
を作成して以下を記述してください。setting.json
ではなく settings.json
なので注意してください。なお、最後の
"css.lint.unknownAtRules": "ignore" の設定によって
src/index.css
で表示されていたエラーが抑制されます。
{
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.formatOnType": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"css.lint.unknownAtRules": "ignore"
}以上で設定が完了です。
VSCodeで src/App.tsx を開くと、以下のように
className
属性に警告(黄色の波線)が表示されています。これはTailwindユーティリティクラス
並び順がよくないよ
という警告です。
この警告状態のまま、保存操作をすると次のように自動でクラスの並び替えをしてくれます。
以上で、基本的な環境構築は完了です。
5.1.1 演習 (宿題: 30分)
プロジェクトフォルダをリネームするか削除して、再度、環境構築に取り組み、大まかな手順を覚えてください。可能であれば、3回、4回と繰り返してください (慣れれば、5分程度で一連の環境構築が可能になります)。
6 Reactアプリケーションの実行順序の理解
Viteで構築したReactプロジェクトを 開発モード
(npm run dev) で実行したときに、各ファイル
(各コンポーネント) が読み込まれていく順番について解説します
(特にここでは「分かりやすさ」を優先するため、厳密な説明ではない部分があるので注意してください)。
まず一番初めにプロジェクトフォルダ直下にある
index.html
が読み込まれます。このようにプログラム実行の開始点 (起点)
となるものを エントリーポイント
といいます。
<!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>次に、上記の 第11行目 に記述される
<script type="module" src="/src/main.tsx"></script>
によって /src/main.tsx が読み込まれます。
そして main.tsx の 第06行目 の
document.getElementById('root')
によって、index.html の 第10行目 の
<div id="root"></div> の領域(idが
root のdiv要素)に対して
<StrictMode><App /></StrictMode>
というコンポーネントがレンダリング (描画) されます。
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)ここでレンダリングされる <App />
コンポーネントの実体は src/main.tsx の
第03行目 で読み込んでいる App.tsx
となります。なお、<StrictMode>...<StrictMode>
コンポーネントについては、いまは無視してください
(気になる人は検索か生成AIで解決してください)。
そして、以下のような 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-md bg-blue-500 px-4 py-0.5 text-white hover:bg-blue-700"
onClick={countUp}
>
count is {count}
</button>
</div>
);
};
export default App;以上、整理すると開発モードでは、以下のような順番でファイルが読み込まれていくと考えてください (プログラムがネスト構造になっていると考えてください)。
index.htmlが読み込まれ、その内部で …src/main.tsxが読み込まれ、その内部で …src/App.tsx(Appコンポーネント) が読み込まれる。
また、この先で
Appコンポーネントのなかに、TodoListコンポーネント
(src/TodoList.tsx) を配置して、さらに、その内部に
TodoItemコンポーネント
(src/TodoItem.tsx)
を配置していきます。その場合は以下のようになります。
index.htmlが読み込まれ、その内部で …src/main.tsxが読み込まれ、その内部で …src/App.tsx(Appコンポーネント) が読み込まれ、その内部で …src/TodoList.tsx(TodoListコンポーネント) が読み込まれ、その内部で …src/TodoItem.tsx(TodoItemコンポーネント) が読み込まれる。
6.1 ビルド (バンドル) 出力
ビルド (npm run build)
を実行すると、最適化されたプロダクトが「distフォルダ」に出力されます。
このプロダクトは トランスパイル
(TypeScript\(\to\)JavaScript)、最適化、バンドル
などの処理によって
ロジックとファイルが集約されたもので、index.html
をエントリポイントとして assets/index-XXXX.js や
assets/index-XXXX.css
が読み込まれるシンプルな構成になります。
実際にビルドを実行して「distフォルダ」の各ファイルの内容について確認してみてください。
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-md bg-blue-500 px-4 py-0.5 text-white hover:bg-blue-700"
onClick={countUp}
>
count is {count}
</button>
</div>
);
};
export default App;第02行目で「WelcomeMessageコンポーネント」を読込み、第13行目で
<WelcomeMessage name="寝屋川タヌキ" />
のように組み込んでいます。ここでは name
という引数が、どのようにWelcomeMessageコンポーネントに渡されているか
(対応関係)
をよく確認してください。例えば「name="寝屋川タヌキ"
ではなく userName="寝屋川タヌキ"
のように名前をウェルカムメッセージコンポーネントに渡したいときは、WelcomeMessage.tsx
のどこを書き換えるべきか」が分かるでしょうか?
なお、JSX要素のなかでは、コメントは
{/* コメント */} のように記述します。
Reactの関数コンポーネントはパスカルケースで名前を設定すること
TypeScriptでは、通常、関数名には キャメルケース (小文字から開始) を使用します。ただし、Reactの関数コンポーネントは、パスカルケース (大文字から開始) を使用する必要があります (また、これにあわせてファイル名も キャメルケース にします)。この命名規則から外れていると、次のように JSX構文のなかでコンポーネントが適切に認識されません。注意してください。
7.1.1 演習
実際に「WelcomeMessageコンポーネント」を作成して、「Appコンポーネント」のなかに組み込んで組み込んでください。また、開発モードで実行結果を確認してください。
また、以下の要求を満たすようにプログラムを書き換えてください。
- ウェルカムメッセージに表示される名前を「萱島ウサギ」に変えたい。
<WelcomeMessage name="萱島ウサギ" uncompletedCount={6}/>のように未完了タスクの数をコンポーネントに渡し、「こんにちは、萱島ウサギさん。現在の未完了タスクは6個です。」のようにウェルカムメッセージを表示したい。- コンポーネントに数値を渡したいときは
uncompletedCount=6ではなくuncompletedCount={6}のようにしてください。 - ウェルカムメッセージコンポーネントが
uncompletedCountを受け取るためには、WelcomeMessage.tsxのなかのPropsの型定義の変更が必要です。 - 解答例はこちら
- コンポーネントに数値を渡したいときは
7.2 状態管理
Reactアプリでは アプリ画面上で変化する値 を 状態(State) という特別な仕組みで管理します。この「状態」が更新されると、Reactが変更を検知し、関連するコンポーネントだけを自動的に再レンダリング (つまり再描画して値の変更を反映) します。
何らかの値 (例えば「現在のカウント」)
を「状態」として扱い、その変更に応じて画面を再レンダリングするには、通常の変数として定義するのでは不十分であり、フックス (Hooks) という機能を利用して値を管理
(保持・更新)
することが求められます。Reactには、状態管理のために様々な Hooks
が用意されていますが、そのなかでも特に基本的な役割を果たすものが
useState という フック (Hook) になります。
サンプルとして示したアプでは、カウント値が更新されたときに、それを即座に画面に自動反映させたいので、第04行目
のように useState を使って、カウント値を保持する変数
count と、それを更新するための専用関数
setCount
を導入しています。なお、useState
の引数には初期値を与えます
(例えば、カウントを「7」から開始したいときは const [count, setCount] = useState(7);
とします)。
useState(0)はuseState<number>(0)のように 扱う値の型を明示して初期化 することも可能です。
そして、現在のカウント値を得たいときは 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-md 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-md 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
は正常に更新されていること が確認できます。
これらのことから「変更をトリガーに再レンダリングする必要ない値」については通常の変数として扱い、逆に「値の変更をトリガーに再レンダリングしたいもの」は useState を使って「状態」として扱う 必要があることが分かると思います。
7.3 練習
次のように、カウント値の表示部をボタンの外に移動し、ボタンを「1Up」と「10Up」の2個に増やすようにアプリを拡張してみます。
まずは、以下のようにプログラムを書き替え、実行結果とプログラムを照らし合わせてプログラムについて理解してください。
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 space-x-2">
<button
className="rounded-md bg-blue-500 px-4 py-2 text-white hover:bg-blue-700"
onClick={countUp1}
>
1 Up
</button>
<button
className="rounded-md bg-blue-500 px-4 py-2 text-white hover:bg-blue-700"
onClick={countUp10}
>
10 Up
</button>
</div>
</div>
);
};
export default App;- 第16行目 の div要素の className属性 の
mb-2をmb-6に変更すると画面がどのようになるか確認してください。また、同様にmt-6、ml-6、my-6に変更するどのようになるか確認してください。mbは margin-bottom の略であることに気付いたでしょうか。- 参考 :Tailwind CSS - Margin
- 第22行目 の div要素の className属性 から
flexを削除するとどのようになるか確認してください。確認後は元に戻しておいてください。 - 第22行目 の div要素の className属性 の
space-x-2をspace-x-4に変更するとどのようになるか確認してください。確認後は元に戻しておいてください。
このプログラムには冗長な記述が多いのでリファクタリングしていきます。関数
countUp1 と countUp10
は、以下のように省略形で (1行で) 書き換えが可能です。前回講義で学んだアロー関数形式の応用です。
const countUp1 = () => {
const newCount = count + 1;
setCount(newCount);
};
const countUp10 = () => {
const newCount = count + 10;
setCount(newCount);
};実際に書き換えて、問題なく動作することが確認してください。
さらに、関数が引数を受け取るように変更して、onClick
での呼び出しを onClick={() => countUpN(1)}
や onClick={() => countUpN(10)}
とすると、さらにスマートに記述ができます (全体のコードはこちらを参照)。
また、関数 countUpN を削除してこちらのように
onClick に 直接処理を記述するような実装
もあります。
7.3.1 演習
次のように、カウントをリセットするボタンを実装してください。
- 実装例はこちら
8 Todoアプリの実装
8.1 準備: ライブラリのインストール
個々の Todo に ID を設定するため UUID (Universally Unique Identifier) v4 を生成可能なuuidというライブラリをインストールします。また、日時の管理を簡単にするためにdayjsとdate-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-bold や
px-4 など)
を動的に結合する際に、スタイリングの衝突を解決してくれるtailwind-mergeをインストールします。開発時のみ必要なので
-D オプションを付けてインストールします。
npm i -D tailwind-merge
- 参考: tailwind-mergeとは
8.2 準備: 初期データの作成
Todoリストの「初期データ」を準備します。
まずは、このTodoアプリで共通使用する「型」を定義しておくためのファイルを
src/types.ts を作成してください
(「src/types.ts
を作成せよ」とは「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」については
deadline を null
として扱うものとします。
次に、Todoリストの初期データを格納するファイルとして
initTodos.ts を作成し、以下のように初期値
initTodos を設定してください。この
initTodo も 他のファイルから参照するので忘れずに
export キーワードを付与してください。
データの内容は自由に変更して問題ありませんが、このあとの展開の関係で
3個以上を設定 してください。
なお、第06行目 の uuid() は UUID
v4 の文字列 (string型) を ランダム
に生成する関数になります (第02行目 では
v4 という関数を uuid
という名前でインポートしています)。
import { 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コンポーネントのテンプレート
が得られます。
ここで定義する TodoList
は、Reactの関数コンポーネントなので必ずパスカルケースとしてください。また、同様に、ファイルの名前もパスカルケースとしてください。
import React from "react";
import { Todo } from "./types";
type Props = {
todos: Todo[];
};
const TodoList = (props: Props) => {
const todos = props.todos;
return (
<div className="space-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型の配列 todos を
Props (プロップス)
として受け取り、先頭から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 { 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 の出力) も確認してください。
src/App.tsx と src/TodoList.tsx
について、生成AIなどを活用して読解・理解をしてください。特に、しっかりと理解してほしい箇所は次のとおりです。
src/App.tsxの 第08行目 : 現時点ではtodosを変更 (追加・削除) する処理は含んでいませんが、今後、それらの機能を実装したときにtodosの変更をトリガーに再レンダリングをしたいのでuseStateを使って「状態」として扱うようにしています。useState<Todo[]>(initTodos)ではなくuseState(initTodos)のように型推論を利用して初期化が可能です。ただし、初期値を空配列にする場合は、明示的に型を与える必要があります。
src/App.tsxの 第09行目 : ここでは、Todo型オブジェクトの配列から「未完了のタスク数」をカウントしています。前回講義で学んだ配列のfilter操作とlengthプロパティを活用している例になります。src/App.tsxの 第23行目 の<TodoList todos={todos} />と、src/TodoList.tsxで定義しているTodoList関数の引数 (Props) が対応している点に注意してください。
8.5 TodoListコンポーネントのブラッシュアップ
現状の src/TodoList.tsx は、todos
の要素数に関わらず 固定で「3件」のTodoしか表示すること
ができません。しかも、todos の要素数が
2個以下のときは実行時エラー が生じます
(Todoが1個も表示されなくなります)。
そこで、前回講義で学んだ mapメソッド を利用して、todos
の要素数にあわせて表示するように変えたいと思います。この
mapメソッドを使うテクニックは
Reactでは定番中の定番処理
となるので確実に読解かつ記述できるようにしてください。
import React from "react";
import { Todo } from "./types";
type Props = {
todos: Todo[];
};
const TodoList = (props: Props) => {
const todos = props.todos;
return (
<div className="space-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 { 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 \(\to\) 2 \(\to\) 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.tsx の JSX
(HTML) と Tailwind CSS
に編集を加えてTodoリストコンポーネントの表示を
自分流にカスタイマイズ
してみてください。これは、UI/UXの設計練習も兼ねています。また、後期前半の大課題に関連する内容にもなってきます。
なお、現時点では「チェックボックス」や「ボタン」などの要素追加やスタイリングは不要です (これらの内容は次回講義で扱います)。
(Todoリストのカスタマイズの例)
- 上記のサンプルのコードはこちら
取組みに際しては、生成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>