バニラJSでTODOアプリを作りながら、引っかかったポイントをQ&A形式でまとめました。
「なんとなく動いてるけど理解できてない」を一個ずつ潰していった記録です。
この記事は 小脳記憶 ser.1 の続きです。
HTMLとJavaScriptの基礎は ser.1 を参照してください。
使った教材
📺 【React18対応】モダンJavaScriptの基礎から始める挫折しないためのReact入門(Udemy)
このコースのバニラJS・TODOアプリ実装セクションで学習した内容をベースに、自分が引っかかったポイントをまとめています。
Q&A:引っかかったポイント集
Q1: 関数って何?なぜ関数にする必要があるの?
A: 関数 = 「受け取る→処理→返す」という一連の動き全体。
function greet(name) {
return `こんにちは、${name}さん!`;
}
// 入力:"じゃけぇ" → 出力:"こんにちは、じゃけぇさん!"
関数にする理由は2つ:
- 何度もやる処理を1つにまとめる(コピーペースト減らす)
- 後で仕様が変わった時に1箇所だけ直せる
Q2: const func3 = (num1, num2) => ({ name: num1 }) の各部分は何?
A:
const func3 = 変数(関数を入れた箱)
(num1, num2) = 引数を受け取る箱
"じゃけぇ","hirosima" = 実際に渡す引数の値
({ name, add }) = オブジェクト(返す結果)
関数 = 「受け取って返す」という動き全体
Q3: ...arr1 の ... は何をしてる?
A: 「配列を1つずつ展開する」だけ。
const arr1 = [1, 2, 3];
sumfunc(...arr1); // → sumfunc(1, 2, 3) に展開される
// 展開した値: 1 2 3
// 引数の箱: num1 num2 箱なし→無視
箱がある分だけ順番に入り、はみ出したものは無視される。
Q4: const [num1, num2, ...arr3] = arr2 の [] は何?
A: 左側の [] = 「配列を分割して変数に入れる」記法(分割代入)
const arr2 = [1, 2, 3, 4, 5];
const [num1, num2, ...arr3] = arr2;
// num1=1, num2=2, arr3=[3,4,5]
... は必ず一番後ろに置く。「残りをまとめる」という動き。
Q5: アロー関数で {} がある時とない時の違いは?
A:
// {} なし → return 不要(自動で返す)
const greet = (name) => `${name}さん`;
// {} あり → return 必要(書かないと返さない)
const greet = (name) => {
const result = `${name}さん`;
return result;
};
処理が1つなら {} なしで書ける。
Q6: div タグって何?コンポーネントとは違う?
A:
div = 箱(何もしない入れ物、まとめるだけ)
コンポーネント = レシピ(箱+中身の動きをまとめたもの)
div はコンポーネントの中の一部として使われるもので、コンポーネント自体ではない。
Q7: ul と li って何?
A:
<ul> ← リストの箱(まとめる入れ物)
<li>TODOです</li> ← リストの1件目
<li>別のTODO</li> ← リストの2件目
</ul>
TODO件数が増え減りするから、リスト構造 ul + li が適している。
Q8: document.createElement("li") はなぜ "<li>" じゃないの?
A: createElement は「タグ名」だけを受け取る設計。
// HTML で書くとき → <> が必要
<li>TODO</li>
// JavaScript で作るとき → タグ名だけ
document.createElement("li")
// innerHTML なら <> を使う
div.innerHTML = "<li>TODO</li>";
Q9: id と class の使い分けは?
A: 「何個あるか」で決まる。
id = ページに1つだけ(ユニーク)
class = 同じものが複数ある
<!-- id → リスト全体(1つしかない) -->
<ul id="incomplete-list"></ul>
<!-- class → TODO項目(何個も作られる) -->
<div class="list-row">...</div>
<div class="list-row">...</div>
Q10: const onClickAdd = () => {} の () が空なのはなぜ?
A: 「こちらが呼ぶ」のではなく「ブラウザが自動で呼ぶ」から。
// こちらが呼ぶ → 引数を渡せる
createIncompleteTodo("買い物");
// ブラウザが呼ぶ → 引数を渡すタイミングがない
addEventListener("click", onClickAdd);
必要な値は関数の中で document.getElementById().value で取得する。
Q11: addEventListener("click", onClickAdd) は何してる?
A: 定義した関数を「いつ使うか」登録している。
// ① 関数を定義(レシピを書く)
const onClickAdd = () => { ... };
// ② その関数を「クリック時に使う」と登録
addEventListener("click", onClickAdd);
// ③ ユーザーがクリック → onClickAddが実行される
Q12: .closest() は親子両方働く?
A: 親側だけに遡る。子方向には探さない。
<ul>
<li> ← ② ここまで遡って見つかる
<div>
<button>完了</button> ← ① ここから探し始める
<p>子要素</p> ← 子方向には探さない
</div>
</li>
</ul>
探す順序:自分 → 親 → 祖父母 → ... → <html>
Q13: .previousElementSibling は何?
A: 「要素の前にある兄弟要素」を取得するプロパティ。
<div>
<p>TODO</p> ← これが previousElementSibling
<button>戻す</button> ← ここから見る
</div>
「親子」ではなく「兄弟」の関係で、前後を取得する。
Q14: DOM操作の「関係性」による分類は?
A:
| 関係 | メソッド/プロパティ | やること |
|---|---|---|
| 親子 | appendChild(子) |
子を追加 |
| 親子 | removeChild(子) |
子を削除 |
| 親子 | firstElementChild |
最初の子を取得 |
| 兄弟 | nextElementSibling |
次の兄弟を取得 |
| 兄弟 | previousElementSibling |
前の兄弟を取得 |
| 探索(上) | closest("セレクタ") |
親を遡って探す |
コード全体:構造理解版
処理の流れを理解しながら読めるよう、コメントを入れています。
import "./styles.css";
// ========================================
// 追加ボタンがクリックされた時の処理
// ========================================
const onClickAdd = () => {
// 1. DOMからid=add-textのvalueを取得しinputTextに格納
const inputText = document.getElementById("add-text").value;
// 2. 上記処理後、DOMのid=add-textの中身を空にする(次の入力の準備)
document.getElementById("add-text").value = "";
// 3. 未完了のリスト作成関数にinputText引数を渡す
createIncompleteTodo(inputText);
};
// ========================================
// 渡された引数を元に未完了のTODOを作成する関数
// ========================================
const createIncompleteTodo = (todo) => {
// ========== STEP 1: 要素を作る ==========
const li = document.createElement("li");
const div = document.createElement("div");
div.className = "list-row";
const p = document.createElement("p");
p.className = "todo-item";
p.innerText = todo; // 引数で受け取ったテキストを表示
// ========== STEP 2: 完了ボタンを作る ==========
const completeButton = document.createElement("button");
completeButton.innerText = "完了";
completeButton.addEventListener("click", () => {
// 親を遡ってliを探す
const moveTarget = completeButton.closest("li");
// 削除ボタン(次の兄弟)を削除
completeButton.nextElementSibling.remove();
// 完了ボタン自身を削除
completeButton.remove();
// 戻すボタンを生成
const backButton = document.createElement("button");
backButton.innerText = "戻す";
backButton.addEventListener("click", () => {
// 前の兄弟要素(p)のテキストを取得
const todoText = backButton.previousElementSibling.innerText;
// 未完了リストに再追加(関数を再利用)
createIncompleteTodo(todoText);
// 完了リストから削除
backButton.closest("li").remove();
});
// liの最初の子(div)に戻すボタンを追加
moveTarget.firstElementChild.appendChild(backButton);
// 完了リストにliを移動
document.getElementById("complete-list").appendChild(moveTarget);
});
// ========== STEP 3: 削除ボタンを作る ==========
const deleteButton = document.createElement("button");
deleteButton.innerText = "削除";
deleteButton.addEventListener("click", () => {
const deleteTarget = deleteButton.closest("li");
document.getElementById("incomplete-list").removeChild(deleteTarget);
});
// ========== STEP 4: 作った要素を組み立てる ==========
div.appendChild(p);
div.appendChild(completeButton);
div.appendChild(deleteButton);
li.appendChild(div);
// ========== STEP 5: 未完了リストに追加(ここで画面に表示) ==========
document.getElementById("incomplete-list").appendChild(li);
};
// ページ読み込み時:追加ボタンにイベントを登録
document.getElementById("add-button").addEventListener("click", onClickAdd);
処理の流れ図解
ユーザーがTODOを入力
↓
「追加」ボタンをクリック
↓
onClickAdd() が実行される
↓
① 入力値を取得 (inputText)
② 入力欄を空にする
③ createIncompleteTodo(inputText) を呼ぶ
↓
createIncompleteTodo() 内で
↓
【要素作成フェーズ】
li, div, p, 完了ボタン, 削除ボタン を作る
↓
【イベント登録フェーズ】
完了ボタン.addEventListener() → 完了時の処理を登録
└→ 中で戻すボタンを作成
└→ 戻すボタン.addEventListener() → 戻す時の処理を登録
削除ボタン.addEventListener() → 削除時の処理を登録
↓
【組み立てフェーズ】
div に p, 完了ボタン, 削除ボタン を追加
li に div を追加
↓
【表示フェーズ】
未完了リストに li を追加 → 画面に表示される
まとめ
今回の学び:
- ✅ 関数 — 「受け取る→処理→返す」のまとまり
- ✅ スプレッド構文
...— 配列を展開する / 残りをまとめる - ✅ アロー関数の
{}あり・なし — returnが必要かどうかが変わる - ✅
idとclassの使い分け — 1つか複数かで決まる - ✅
addEventListener— 関数を「いつ使うか」登録する - ✅ DOM操作の関係性 — 親子・兄弟・探索で使うメソッドが違う
- ✅
.closest()— 親方向にしか探さない - ✅
.previousElementSibling— 前の兄弟要素を取得
次回は、このTODOアプリをReactで書き直してみます。
バニラJSとReactの違いが体感できるはずです。