プロジェクトのセットアップ
準備として、今回はデスクトップに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要素を取得し、それぞれ HTMLInputElement
や HTMLButtonElement
にキャストしています。
TypeScriptでDOM要素を取得する際、document.getElementById
メソッドを使用すると、返される要素の型はデフォルトで HTMLElement | null
です。このままでは、特定のHTML要素に固有のプロパティやメソッドにアクセスできないため、TypeScriptにその要素が具体的にどのタイプのHTML要素であるかを教える必要があります。これを「型キャスト」といいます。
型キャストにより、TypeScriptにその要素が特定の型(例えば、HTMLInputElement
や HTMLButtonElement
)であることを伝えることで、その要素に固有のプロパティやメソッドにアクセスできるようになります。
例えば、 itemInput
が HTMLInputElement
であることをTypeScriptに明示します。その理由は、itemInput
が HTMLInputElement
型であると指定することで、value
プロパティなど HTMLInputElement
に固有のプロパティやメソッドに安全にアクセスできます。
priceInput
も同様に HTMLInputElement
型としてキャストされます。これにより、数値入力フィールドとして扱うことができます。
addButton
は HTMLButtonElement
型としてキャストされます。これにより、ボタン固有のプロパティやメソッドにアクセスできます。
itemList
は 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 = "";
}
});
});
クリックイベントリスナー: addButton
にクリックイベントリスナーを追加し、ボタンがクリックされたときに商品を追加する処理を実行します。
trim()
メソッドは、文字列の先頭と末尾から空白文字を取り除きます。空白文字には、スペース、タブ、改行などが含まれます。
parseFloat()
関数は入力フィールドから取得した文字列を浮動小数点数(数値)に変換して変数に格納する処理を行っています。
renderList();
は関数呼び出しです。この関数は、商品リストをHTMLのリスト要素にレンダリング(描画)するためのものです。
// 商品を追加する関数を定義
function addItem(name: string, price: number): void {
const newItem: Item = { name, price };
items.push(newItem);
addItem
関数: 商品の名前と価格を引数として受け取り、新しい商品をitems
配列に追加します。引数name
とprice
にそれぞれstring
型とnumber
型の型付けをしています。renderList
関数:items
配列の内容を使って、商品リストをHTMLにレンダリングします。
// 商品リストを表示する関数を定義
function renderList(): void {
const itemList = document.getElementById("itemList") as HTMLUListElement;
itemList.innerHTML = "";
itemList
は、HTMLの<ul>
要素を取得しています。この要素は、ショッピングリストの項目を表示するためのリスト要素です。getElementById
メソッドで取得される要素をHTMLUListElement
としてキャストしています。itemList
のinnerHTML
プロパティを空の文字列に設定することで、リスト要素の中身をすべてクリアしています。これにより、リストを再描画する前に既存の項目が削除されます。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
要素に追加します。