1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【小脳記憶 ser.2】バニラJSでTODOアプリを作りながら詰まったこと全部まとめた

1
Posted at

バニラ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つにまとめる(コピーペースト減らす)
  2. 後で仕様が変わった時に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: ulli って何?

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: idclass の使い分けは?

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が必要かどうかが変わる
  • idclass の使い分け — 1つか複数かで決まる
  • addEventListener — 関数を「いつ使うか」登録する
  • ✅ DOM操作の関係性 — 親子・兄弟・探索で使うメソッドが違う
  • .closest() — 親方向にしか探さない
  • .previousElementSibling — 前の兄弟要素を取得

次回は、このTODOアプリをReactで書き直してみます。
バニラJSとReactの違いが体感できるはずです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?