第3章 プロジェクト1: 簡単なタスク管理アプリの作成
これまでに学んだTypeScriptの基礎知識を活用して、実際にWebアプリケーションを作成してみよう。ここでは、簡単なタスク管理アプリを通して、TypeScriptとWebの連携について学んでいく。
アプリの要件は以下の通りだ。
- タスクの追加、削除、フィルタリングができる
- タスクのデータをローカルストレージに保存し、ページをリロードしても状態が維持される
- レスポンシブデザインで、モバイルでも使いやすい
これらの要件を満たすアプリを、一緒に作成していこう。
3.1 アプリの仕様と設計
まずは、アプリの仕様と設計を明確にしておこう。
- タスクは、タイトルと完了状態(完了/未完了)を持つ
- 各タスクはリスト形式で表示され、完了状態に応じてスタイルが変化する
- フォームからタスクを追加できる
- 各タスクには削除ボタンがあり、クリックするとタスクが削除される
- フィルタリング機能で、全てのタスク/完了済みのタスク/未完了のタスクを切り替えて表示できる
これらの仕様を元に、HTMLとCSSでアプリのベースを作成していく。
3.2 HTMLとCSSの基礎
Webアプリケーションを作成するには、HTMLとCSSの知識が必要不可欠だ。ここでは、その基礎について復習しておこう。
3.2.1 HTMLの構造
HTMLは、Webページの構造を記述するためのマークアップ言語だ。タグを使ってコンテンツを構造化する。
<!DOCTYPE html>
<html>
<head>
<title>タスク管理アプリ</title>
</head>
<body>
<header>
<h1>タスク管理アプリ</h1>
</header>
<main>
<!-- ここにアプリの主要な内容を記述 -->
</main>
<footer>
<p>© 2023 タスク管理アプリ</p>
</footer>
</body>
</html>この例では、基本的なHTMLの構造を示している。<head>タグ内にページのメタ情報を、<body>タグ内にコンテンツを記述する。
3.2.2 CSSによるスタイリング
CSSは、HTMLの要素をスタイリングするための言語だ。セレクタを使って要素を指定し、プロパティと値でスタイルを定義する。
/* タイトル */
h1 {
font-size: 24px;
color: #333;
}
/* タスクリスト */
ul {
list-style: none;
padding: 0;
}
li {
margin-bottom: 10px;
}
/* 完了済みのタスク */
.completed {
text-decoration: line-through;
color: #999;
}この例では、h1タグ、ulタグ、liタグ、completedクラスにスタイルを適用している。
3.2.3 レスポンシブデザイン
レスポンシブデザインとは、画面のサイズに応じてレイアウトを最適化する手法だ。メディアクエリを使って、画面サイズに応じたCSSを定義する。
/* デフォルトのスタイル */
body {
font-size: 16px;
}
/* 画面幅が600px以下の場合 */
@media screen and (max-width: 600px) {
body {
font-size: 14px;
}
}この例では、画面幅が600px以下の場合に、bodyタグのfont-sizeを14pxに変更している。
3.3 DOM操作
TypeScriptからHTMLの要素を操作するには、DOMを扱う必要がある。ここでは、よく使われるDOM操作の方法を見ていこう。
3.3.1 要素の取得
document.querySelectorメソッドを使って、HTMLの要素を取得できる。
const title = document.querySelector('h1');この例では、h1タグの要素を取得し、title定数に代入している。
3.3.2 要素の作成と追加
document.createElementメソッドで新しい要素を作成し、appendChildメソッドで他の要素に追加できる。
const newTask = document.createElement('li');
newTask.textContent = 'New Task';
const taskList = document.querySelector('ul');
taskList.appendChild(newTask);この例では、liタグの要素を作成し、テキストを設定した上で、ulタグの要素に追加している。
3.3.3 要素の削除
removeChildメソッドを使って、要素を削除できる。
const taskList = document.querySelector('ul');
const task = document.querySelector('li');
taskList.removeChild(task);この例では、ulタグの要素からliタグの要素を削除している。
3.3.4 要素の属性操作
要素の属性を操作するには、setAttributeメソッドとgetAttributeメソッドを使う。
const task = document.querySelector('li');
task.setAttribute('id', 'task-1');
const taskId = task.getAttribute('id');この例では、liタグの要素にid属性を設定し、その値を取得している。
3.4 イベントハンドリング
ユーザーの操作に応じてアプリの状態を変化させるには、イベントハンドリングが必要だ。ここでは、イベントリスナーの登録方法と、イベントオブジェクトの扱い方を学ぼう。
3.4.1 イベントリスナーの登録
addEventListenerメソッドを使って、要素にイベントリスナーを登録できる。
const button = document.querySelector('button');
button.addEventListener('click', () => {
console.log('Button clicked!');
});この例では、buttonタグの要素にclickイベントのリスナーを登録している。ボタンがクリックされると、コールバック関数が呼び出される。
3.4.2 イベントオブジェクト
イベントリスナーのコールバック関数には、イベントオブジェクトが渡される。このオブジェクトから、イベントに関する情報を取得できる。
const taskList = document.querySelector('ul');
taskList.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log('Task clicked!');
}
});この例では、ulタグの要素にclickイベントのリスナーを登録している。イベントオブジェクトのtargetプロパティから、クリックされた要素を取得し、それがliタグであるかどうかを判定している。
3.4.3 イベントの伝播
イベントは、DOM階層を通って伝播する。キャプチャフェーズ、ターゲットフェーズ、バブリングフェーズの3つのフェーズがある。
- キャプチャフェーズ:イベントが階層を下る
- ターゲットフェーズ:イベントが目的の要素に到達する
- バブリングフェーズ:イベントが階層を上る
const taskList = document.querySelector('ul');
const task = document.querySelector('li');
taskList.addEventListener('click', () => {
console.log('Clicked on task list!');
}, true);
task.addEventListener('click', (event) => {
console.log('Clicked on task!');
event.stopPropagation();
});この例では、ulタグの要素にclickイベントのリスナーを登録している。第3引数にtrueを渡しているため、キャプチャフェーズでイベントが発火する。また、liタグの要素にもイベントリスナーを登録し、stopPropagationメソッドを呼び出してイベントの伝播を止めている。
3.5 タスク管理アプリの実装
ここまでの知識を活用して、いよいよタスク管理アプリを実装していこう。
3.5.1 タスクの追加機能
まずは、タスクを追加する機能から実装しよう。
const form = document.querySelector('form');
const taskInput = document.querySelector('#task-input');
const taskList = document.querySelector('ul');
form.addEventListener('submit', (event) => {
event.preventDefault();
const taskTitle = taskInput.value;
if (taskTitle.trim() !== '') {
const newTask = document.createElement('li');
newTask.textContent = taskTitle;
taskList.appendChild(newTask);
taskInput.value = '';
}
});この例では、フォームの送信イベントをリッスンしている。フォームが送信されると、イベントのデフォルトの動作をキャンセルし、入力されたタスク名を取得する。タスク名が空でない場合、新しいli要素を作成し、タスクリストに追加する。
3.5.2 タスクの削除機能
次に、タスクを削除する機能を実装しよう。
taskList.addEventListener('click', (event) => {
if (event.target.tagName === 'BUTTON') {
const task = event.target.parentElement;
taskList.removeChild(task);
}
});この例では、タスクリストのclickイベントをリッスンしている。クリックされた要素がボタンである場合、そのボタンの親要素(タスク)をタスクリストから削除する。
3.5.3 タスクのフィルタリング機能
タスクのフィルタリング機能を実装してみよう。
const filterButtons = document.querySelectorAll('.filter-button');
filterButtons.forEach((button) => {
button.addEventListener('click', () => {
const filter = button.getAttribute('data-filter');
const tasks = taskList.querySelectorAll('li');
tasks.forEach((task) => {
if (filter === 'all' || task.classList.contains(filter)) {
task.style.display = 'block';
} else {
task.style.display = 'none';
}
});
});
});この例では、フィルタボタンのclickイベントをリッスンしている。クリックされたボタンのdata-filter属性からフィルタ条件を取得し、各タスクに対して条件に合うかどうかを判定する。条件に合うタスクは表示し、合わないタスクは非表示にする。
3.5.4 ローカルストレージを用いたデータの永続化
最後に、タスクのデータをローカルストレージに保存し、ページをリロードしても状態が維持されるようにしよう。
const saveTasks = () => {
const tasks = taskList.querySelectorAll('li');
const taskTitles = Array.from(tasks).map((task) => task.textContent);
localStorage.setItem('tasks', JSON.stringify(taskTitles));
};
const loadTasks = () => {
const taskTitles = JSON.parse(localStorage.getItem('tasks'));
if (taskTitles) {
taskTitles.forEach((title) => {
const newTask = document.createElement('li');
newTask.textContent = title;
taskList.appendChild(newTask);
});
}
};
form.addEventListener('submit', (event) => {
// ...
saveTasks();
});
taskList.addEventListener('click', (event) => {
// ...
saveTasks();
});
loadTasks();この例では、saveTasks関数でタスクのデータを取得し、ローカルストレージに保存している。loadTasks関数では、ローカルストレージからデータを取得し、タスクリストに反映している。フォームの送信時とタスクの削除時にsaveTasks関数を呼び出し、ページの読み込み時にloadTasks関数を呼び出すことで、データの永続化を実現している。
3.6 アプリのリファクタリングと最適化
実装が完了したら、コードのリファクタリングと最適化を行おう。
3.6.1 コードの分割とモジュール化
関連する機能ごとにコードを分割し、モジュール化することで、コードの可読性とメンテナンス性を向上させよう。
// task.ts
export class Task {
// ...
}
// taskList.ts
import { Task } from './task';
export class TaskList {
// ...
}
// app.ts
import { TaskList } from './taskList';
const taskList = new TaskList();
// ...この例では、TaskクラスとTaskListクラスを別のファイルに分割し、app.tsでそれらをインポートしている。
3.6.2 パフォーマンスの改善
パフォーマンスのボトルネックとなる部分を特定し、改善することで、アプリの応答性を高めよう。
- DOMアクセスを最小限に抑える
- イベントリスナーの数を減らす
- 不要な再描画を避ける
3.6.3 エラーハンドリング
想定外のエラーによってアプリが停止しないように、適切にエラーをハンドリングしよう。
try {
// エラーが発生する可能性のある処理
} catch (error) {
console.error('An error occurred:', error);
// エラー時の処理
}この例では、tryブロック内でエラーが発生する可能性のある処理を行い、catchブロックでエラーをキャッチしている。エラーが発生した場合、コンソールにエラーメッセージを出力し、適切なエラー処理を行う。
これで、簡単なタスク管理アプリの作成を通して、TypeScriptとWebの連携について学ぶことができた。
プロジェクトを通して、以下のようなスキルが身についたはずだ。
- HTMLとCSSを使ったWebページの構造化とスタイリング
- TypeScriptからのDOM操作
- イベントハンドリングとユーザーインタラクションの実装
- ローカルストレージを使ったデータの永続化
- コードの分割とモジュール化によるメンテナンス性の向上
- パフォーマンスの改善とエラーハンドリング
これらのスキルは、モダンなWebアプリケーション開発に欠かせないものばかりだ。今後、さらに複雑なアプリケーションを作成する際にも、この経験が役立つはずだ。
ただし、このアプリはあくまでも基本的な機能しか持っていない。実際のアプリケーション開発では、以下のような点も考慮する必要がある。
- ユーザー認証と認可
- データベースとの連携
- APIとのデータのやり取り
- 状態管理(Redux, MobXなど)
- UIフレームワークの活用(React, Angularなど)
これらの技術を習得することで、より洗練されたWebアプリケーションを開発できるようになるだろう。
次のプロジェクトでは、これらの技術の一部を取り入れつつ、さらに実践的なアプリケーション開発に挑戦してみよう。
プロジェクト1で学んだ基礎をしっかりと身につけ、新しい技術にも積極的にチャレンジしていってほしい。TypeScriptとWebの可能性は無限大だ。自分の創造力を存分に発揮し、ユーザーに価値を提供できるアプリケーションを作り上げていこう!
がんばって開発を続けていってほしい。次のプロジェクトでも、きっと大きな成長が待っているはずだ。楽しみにしている!