JavaScript初心者がTodoリストを作って学んだことまとめ

## はじめに

JavaScriptの学習を進める中で、学んだことのアウトプットとして、**シンプルなTodoリスト作成**に挑戦しました。

この記事では、その作成を通して得られた学びや気づきを備忘録としてまとめます。

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3841606/223a7562-3f08-454d-b16d-cad6146f6c7b.gif)

## 概要

HTML・CSS（Bootstrap）・JavaScript を用いて作成したシンプルな Todo リストです。

以下の基本機能を備えています。

- タスクの追加
    入力欄に文字を入力し、「追加」ボタンを押すとリストにタスクが追加されます
    
- タスクの削除
    各タスクに表示される「削除」ボタンを押すと、そのタスクがリストから削除されます
    
- タスクの編集
    「編集」ボタンを押すと、タスクを編集可能な状態に切り替わります。編集後、「保存」または「キャンセル」が選べます
    
- 完了状態の切り替え
    チェックボックスのオン/オフでタスクの完了状態を切り替えることができ、完了したタスクには打ち消し線や文字色の変更が適用されます

## 学んだこと

### `template`要素の活用

`<ul><li>`のような基本的なリスト要素であれば、`createElement`を使って動的に追加・削除する方法を学びました。

しかし、より複雑なUIを構築する際に、一つひとつの要素を`createElement`で地道に作成していく必要があるのかと悩んでいました。

そこで、今回`template`要素が非常に便利だと学びました。`template`要素を使うことで、HTMLの構造をテンプレートとして定義し、JavaScriptから簡単に複製してDOMに追加できるため、効率的にUIを構築できます。

---

### `cloneNode`とHTML要素

`template`要素を複製する際に`cloneNode()`メソッドを使いますが、ここで一つ注意点がありました。`template`要素の`content`プロパティを`cloneNode(true)`で複製すると、返されるのは通常のHTML要素（`HTMLElement`）ではなく、`DocumentFragment`という特殊な型になる点です。
そのため、`DocumentFragment` の中から1つの `HTMLElement` を `querySelector()` で取り出す必要がありました。

```jsx
const template = document.getElementById("todo-template");
const newClone = template.content.cloneNode(true);  // DocumentFragment
const oldItem = document.querySelector(".todo-item");  // HTMLElement

// ❌ エラー（DocumentFragment, HTMLElement）
oldItem.parentElement.replaceChild(newClone, oldItem);

// ✅ 成功（HTMLElement, HTMLElement）
const newItem = newClone.querySelector(".todo-item");   // HTMLElement
oldItem.parentElement.replaceChild(newItem, oldItem);
```

---

### イベント委譲

複数のTodoアイテムがある場合、どのTodoに対する操作なのかを特定するのが課題でした。

個々の要素にイベントリスナーを設定する方法では、動的に生成される要素の対応がわかりませんでした。

そこで、**イベント委譲**という手法を学びました。

これは、個々の要素にイベントリスナーを設定するのではなく、それらの親要素にイベントリスナーを設定し、クリックされた子要素を`event.target`で判別するという方法です。

この手法のおかげで、動的に追加されるTodoアイテムにも柔軟に対応できるようになりました。

```jsx
// 親要素にクリックイベントを設定（イベント委譲）
todoList.addEventListener("click", (event) => {
  if (event.target.classList.contains("delete-button")) {
    // クリックされたボタンの親の.todo-itemを取得
    const item = event.target.closest(".todo-item");
    
    // delete処理
  }
});
```

---

### `dataset`属性によるデータ管理

`id`の代わりに、HTML要素にカスタムデータを格納できる`dataset`属性（`data-*`属性）を使って、TodoアイテムのIDを管理する方法を学びました。これにより、要素に紐づく情報をHTML側でシンプルに保持でき、JavaScriptからのアクセスも容易になります。

```jsx
const clone = template.content.cloneNode(true);
const todoItem = clone.querySelector(".todo-item");
todoItem.dataset.id = id; // ここでIDをHTMLに埋め込む
```

---

### findIndexとvalueOfの使い方

Todoリストで特定のタスクを更新したり削除したりする際、配列の中から該当のTodoアイテムを正確に特定する必要がありました。ここでオブジェクトの比較について深く学ぶことになりました。

当初、`valueOf()`メソッドを使えばオブジェクトの内容を比較できると考え、以下のように記述しました。

```jsx
const todos = [
  { id: 1, name: "task1" },
  { id: 2, name: "task2" },
];

const target = { id: 2, name: "task2" };

const index = todos.findIndex((item) => item.valueOf() === target.valueOf());
console.log(index); // -1 ❌ 見つからない
```

しかし、JavaScriptでオブジェクト同士を`===`で比較する際は**参照の比較**が行われます。`valueOf()`はデフォルトでオブジェクト自身を返すため、たとえ内容が同じでも参照が異なれば常に`false`となるのです。

この課題に対し、タスクの一意な識別子である`id`プロパティを用いて比較することで解決できました。これには`Array.prototype.findIndex()`メソッドが非常に有効でした。

```jsx
const todos = [
  { id: 1, name: "task1" },
  { id: 2, name: "task2" },
];

const targetId = 2;

const index = todos.findIndex((item) => item.id === targetId);
console.log(index); // 1 ✅
```

## 感想

JavaScriptを学んでいく中で、自分の手で動くWebページを作れるようになり、ワクワクしました。

今回はシンプルなTodoリストでしたが、今後はもう少し複雑な機能にも挑戦していきたいです。