第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>&copy; 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-size14pxに変更している。

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の可能性は無限大だ。自分の創造力を存分に発揮し、ユーザーに価値を提供できるアプリケーションを作り上げていこう!

がんばって開発を続けていってほしい。次のプロジェクトでも、きっと大きな成長が待っているはずだ。楽しみにしている!