【実践】「ショッピングリストアプリ」を作成してみましょう。

プロジェクトのセットアップ

準備として、今回はデスクトップにshoppinglistフォルダを作成しました。

npmプロジェクトの初期化

コマンドよりnpmプロジェクトを初期化します。これにより、package.jsonファイルが作成されます。

npm init -y

TypeScriptをプロジェクトに追加

プロジェクトにTypeScriptをローカルインストールします。これにより、package-lock.jsonファイルが作成されます。

npm install typescript --save-dev

4. TypeScriptコンパイラの設定

tsconfig.jsonファイルを作成して、TypeScriptコンパイラの設定を行います。

npx tsc --init

これにより、基本的な設定が含まれたtsconfig.jsonファイルが生成されます。このファイルを編集して、プロジェクトに適した設定を行います。

プロジェクト構造

shoppinglist
├── dist/
│ └── app.js
├── src/
│ └── app.ts
├── index.html
└── style.css

準備が整ったところで、まずはhtmlを作ってみます。index.html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TypeScript Shopping List App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app">
<h1>Shopping List</h1>
<input type="text" id="itemInput" placeholder="Item name">
<input type="number" id="priceInput" placeholder="Price">
<button id="addButton">Add Item</button>
<ul id="itemList"></ul>
</div>
<script src="dist/app.js"></script>
</body>
</html>

続いてCSSでスタイルを整えます。style.css

#app {
max-width: 400px;
margin: 50px auto;
text-align: center;
}

input {
padding: 10px;
width: 80%;
margin-bottom: 10px;
box-sizing: border-box;
}

button {
padding: 10px 20px;
margin-bottom: 20px;
}

ul {
list-style-type: none;
padding: 0;
}

li {
padding: 10px;
border: 1px solid #ddd;
margin-bottom: 5px;
text-align: left;
}

ここまでの実装結果がこちらです。

TypeScriptでコードを書いていきます。src/app.ts

interface Item {
name: string;
price: number;
}

let items: Item[] = [];

// DOMが完全に読み込まれたときに実行される関数を設定
document.addEventListener("DOMContentLoaded", () => {
const itemInput = document.getElementById("itemInput") as HTMLInputElement;
const priceInput = document.getElementById("priceInput") as HTMLInputElement;
const addButton = document.getElementById("addButton") as HTMLButtonElement;
const itemList = document.getElementById("itemList") as HTMLUListElement;

addButton.addEventListener("click", () => {
const itemName = itemInput.value.trim();
const itemPrice = parseFloat(priceInput.value);

if (itemName !== "" && !isNaN(itemPrice)) {
addItem(itemName, itemPrice);
renderList();
itemInput.value = "";
priceInput.value = "";
}
});
});

// 商品を追加する関数を定義
function addItem(name: string, price: number): void {
const newItem: Item = { name, price };
items.push(newItem);
}

// 商品リストを表示する関数を定義
function renderList(): void {
const itemList = document.getElementById("itemList") as HTMLUListElement;
itemList.innerHTML = "";

items.forEach(item => {
const listItem = document.createElement("li");
listItem.textContent = `${item.name} - $${item.price.toFixed(2)}`;
itemList.appendChild(listItem);
});
}

コードの解説

型定義と変数の宣言

interface Item {
name: string;
price: number;
}

let items: Item[] = [];
  • Item インターフェース: 商品の名前 (name) と価格 (price) を持つ型を定義しています。
  • items 配列: Item 型のオブジェクトを格納する配列です。商品のリストを保持します。

DOMContentLoaded イベント

document.addEventListener("DOMContentLoaded", () => {
const itemInput = document.getElementById("itemInput") as HTMLInputElement;
const priceInput = document.getElementById("priceInput") as HTMLInputElement;
const addButton = document.getElementById("addButton") as HTMLButtonElement;
const itemList = document.getElementById("itemList") as HTMLUListElement;

要素の取得と型キャスト: DOM要素を取得し、それぞれ HTMLInputElementHTMLButtonElement にキャストしています。

TypeScriptでDOM要素を取得する際、document.getElementById メソッドを使用すると、返される要素の型はデフォルトで HTMLElement | null です。このままでは、特定のHTML要素に固有のプロパティやメソッドにアクセスできないため、TypeScriptにその要素が具体的にどのタイプのHTML要素であるかを教える必要があります。これを「型キャスト」といいます。

型キャストにより、TypeScriptにその要素が特定の型(例えば、HTMLInputElementHTMLButtonElement)であることを伝えることで、その要素に固有のプロパティやメソッドにアクセスできるようになります。

例えば、 itemInputHTMLInputElement であることをTypeScriptに明示します。その理由は、itemInputHTMLInputElement 型であると指定することで、value プロパティなど HTMLInputElement に固有のプロパティやメソッドに安全にアクセスできます。

priceInput も同様に HTMLInputElement 型としてキャストされます。これにより、数値入力フィールドとして扱うことができます。

addButtonHTMLButtonElement 型としてキャストされます。これにより、ボタン固有のプロパティやメソッドにアクセスできます。

itemListHTMLUListElement 型としてキャストされます。これにより、リスト要素として扱うことができます。

addButton.addEventListener("click", () => {
const itemName = itemInput.value.trim();
const itemPrice = parseFloat(priceInput.value);

if (itemName !== "" && !isNaN(itemPrice)) {
addItem(itemName, itemPrice);
renderList();
itemInput.value = "";
priceInput.value = "";
}
});
});

クリックイベントリスナー: addButton にクリックイベントリスナーを追加し、ボタンがクリックされたときに商品を追加する処理を実行します。

trim() メソッドは、文字列の先頭と末尾から空白文字を取り除きます。空白文字には、スペース、タブ、改行などが含まれます。

parseFloat() 関数は入力フィールドから取得した文字列を浮動小数点数(数値)に変換して変数に格納する処理を行っています。

renderList(); は関数呼び出しです。この関数は、商品リストをHTMLのリスト要素にレンダリング(描画)するためのものです。

// 商品を追加する関数を定義
function addItem(name: string, price: number): void {
const newItem: Item = { name, price };
items.push(newItem);
  • addItem 関数: 商品の名前と価格を引数として受け取り、新しい商品を items 配列に追加します。引数 nameprice にそれぞれ string 型と number 型の型付けをしています。
  • renderList 関数: items 配列の内容を使って、商品リストをHTMLにレンダリングします。
// 商品リストを表示する関数を定義
function renderList(): void {
const itemList = document.getElementById("itemList") as HTMLUListElement;
itemList.innerHTML = "";
  • itemList は、HTMLの <ul> 要素を取得しています。この要素は、ショッピングリストの項目を表示するためのリスト要素です。
  • getElementById メソッドで取得される要素を HTMLUListElement としてキャストしています。
  • itemListinnerHTML プロパティを空の文字列に設定することで、リスト要素の中身をすべてクリアしています。これにより、リストを再描画する前に既存の項目が削除されます。
  • renderList(); という行は、この renderList 関数を呼び出して実行します。関数が呼び出されると、上記の処理が順に実行され、最新の商品のリストがHTMLに描画されます。
 items.forEach(item => {
const listItem = document.createElement("li");
listItem.textContent = `${item.name} - $${item.price.toFixed(2)}`;
itemList.appendChild(listItem);
});
}

items 配列の各項目について、以下の処理を行います:

  • 新しい <li> 要素を作成します。
  • この <li> 要素のテキスト内容を、商品名と価格を含む文字列に設定します。価格は toFixed(2) メソッドを使って小数点以下2桁にフォーマットされています。
  • 作成した <li> 要素を、itemList 要素に追加します。

実装結果