0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[JavaScript]基本のCRUD

Last updated at Posted at 2026-02-05

JavaScript初心者がUdemyでToDoリストでCRUDを学ぼうとしたら、再帰とクロージャの沼にハマって納得しました。

Udemyで学んだToDリストを自分なりに温泉Wishリストに変形して1から書いてみたら、関数が再帰していることに気が付き、なんで無限ループにならないのかと疑問を感じました。
無限ループにならない理由はクリックイベントリスナーがついているので、ユーザーがクリックするまで関数が実行されないためでした。

image.png

index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JavaScriptで温泉ウィッシュリストを作る練習</title>
  </head>
  <body>
    <h1>温泉ウィッシュリスト</h1>
    <div id="input-area">
      <input id="input_form" placeholder="行きたい温泉を入力" />
      <button id="addBtn">追加</button>
    </div>
    <div id="incomplete-area">
      <h1>行ってみたい温泉</h1>
      <ul id="wish-list">
        <!-- <li>
          <div class="list-row">
            <p>運動</p>
            <button>行った</button>
            <button>削除</button>
          </div>
        </li>
        <li>
          <div class="list-row">
            <p>旅行</p>
            <button>行った</button>
            <button>削除</button>
          </div>
        </li> -->
      </ul>
    </div>
    <div id="complete-area">
      <h1>いったことがある温泉</h1>
      <ul id="done_list">
        <!-- <li>
          <div class="list-row">
            <p>Udemy勉強</p>
            <button>戻す</button>
          </div>
        </li> -->
      </ul>
    </div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

index.js

import './style.css';

// 追加ボタンを押した時に実行したい関数を定義
const onClickAdd = () => {
  // inputタグに入力された文字列を取得
  const onsen = document.getElementById('input_form').value;
  document.getElementById('input_form').value = '';

  createWishList(onsen);
};

// いってみたいリストの1行を作成する関数
const createWishList = (onsen) => {
  // 追加する要素を作成
  const li = document.createElement('li');
  const div = document.createElement('div');
  div.classList.add('list-row');
  const p = document.createElement('p');
  p.textContent = onsen;
  // 削除ボタンを作成
  const goBtn = document.createElement('button');
  goBtn.textContent = '行った';
  // 行ったボタンの機能を追加
  goBtn.addEventListener('click', () => {
    const moveTarget = goBtn.closest('li');
    goBtn.nextSibling.remove();
    goBtn.remove();
    // 戻すボタンを作成
    const backButton = document.createElement('button');
    backButton.textContent = '戻す';
    // 戻すボタンの機能(未来のイベントを作る)
    backButton.addEventListener('click', () => {
      createWishList(onsen);
      backButton.closest('li').remove();
    });
    moveTarget.firstElementChild.appendChild(backButton);
    const ul = document.getElementById('done_list');
    // console.log(ul);
    ul.appendChild(moveTarget);
  });
  // 削除ボタンを作成
  const deleteBtn = document.createElement('button');
  deleteBtn.textContent = '削除';
  // 削除ボタンの機能を追加
  deleteBtn.addEventListener('click', () => {
    deleteBtn.closest('li').remove();
  });

  // 要素の階層を作成
  div.appendChild(p);
  div.appendChild(goBtn);
  div.appendChild(deleteBtn);
  li.appendChild(div);
  // console.log(li);

  const ul = document.getElementById('wish-list');
  ul.appendChild(li);
};

// 追加ボタンにイベントリスナーさんを追加しみはらせる
document.getElementById('addBtn').addEventListener('click', onClickAdd);

style.css

html {
  color: #666;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 16px;
}
h1 {
  font-weight: bold;
  font-size: 1.6rem !important;
  margin-top: 0px;
  text-align: center;
}
button {
  background-color: lightgrey;
  border-radius: 6px;
  border: none;
  padding: 6px 14px;
}
button:hover {
  background-color: burlywood;
}

input {
  padding: 6px 12px;
  border-radius: 6px;
  border: none;
  margin-right: 6px;
}
.list-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
#input-area {
  background-color: bisque;
  padding: 10px;
  border-radius: 6px;
  margin-bottom: 16px;
}
#incomplete-area {
  border-radius: 6px;
  border: 2px solid burlywood;
  padding: 16px;
  margin-bottom: 16px;
  min-height: 50px;
}
#complete-area {
  border-radius: 6px;
  background-color: bisque;
  padding: 16px;
  margin-bottom: 16px;
  min-height: 50px;
}

1. 「再帰」とはどういう意味?

プログラミングにおける再帰(Recursion)とは、 「ある関数の中で、自分自身の関数を呼び出すこと」 を指します。

今回のコードは、まさにその形になっています。

createWishList の中で

backButton(戻すボタン)を作り

そのボタンが押されたら、また createWishList を実行する

2. なぜこの方法(再帰)を使うのか?

この「戻すボタン」の処理は 「卵が先か、鶏が先か」 みたいな状態になっているからです。

問題: createWishList 関数を作るときには、まだ「戻すボタン」は存在しない。

解決: でも、「戻すボタン」を押したときにやりたいことは、結局「createWishList 関数をもう一度動かすこと」そのもの。

そこで、 「あ、さっき作った自分(関数)をもう一回使い回せばいいじゃん!」 と考えるのが、この再帰的な書き方です。これによって、同じ処理(タグを作って、クラスをつけて、イベントをつけて…)を二度書かなくて済むようになります。

3. もし再帰を使わないと

もし「戻すボタンの作成」を createWishList の外に出そうとすると、逆に管理が大変になります。

外に出した場合:「どのボタン」が「どの温泉名」を「どこのリスト」に戻すべきか、毎回探し回る処理が必要になります。

中に入れた場合(今のコード):関数が動いた瞬間に、その時の onsen(温泉名)という変数をボタンが記憶(保持)してくれます。

この「関数が実行された時の環境を、中身の処理がずっと覚えている」仕組みを専門用語で クロージャ(Closure) と呼びます。今の実装は、このクロージャーという仕組みを使っています。

4.クロージャーとは

クロージャは単なる「関数の種類」ではなく、 「関数とその関数が作られた時の環境(変数など)をセットで閉じ込めたもの」という仕組み(現象) のことです。

「クロージャ」という言葉、なんだかイメージしにくいものです。

結論から言うと、クロージャは単なる「関数の種類」ではなく、「関数とその関数が作られた時の環境(変数など)をセットで閉じ込めたもの」という仕組み(現象) のことです。

わかりやすいイメージ:「記憶を持った関数」

普通、関数の中で作った変数は、その関数の処理が終わると消えてしまいます(使い捨て)。 しかし、クロージャという仕組みを使うと、「関数が死んでも、その中身(データ)だけは、特定の場所で生き続ける」 という不思議なことが起こります。

今回のコードで言うと、ここがクロージャの恩恵を受けている部分です:

const createWishList = (onsen) => { // 外側の関数(親)
  // ...省略...
  backButton.addEventListener('click', () => { // 内側の関数(子)
    // この関数は「onsen」という名前をずっと覚えている!
    createWishList(onsen); 
  });
};
  • createWishList("草津温泉") として実行。
  • その瞬間、この関数の中に「草津温泉」という文字が保存されます。
  • 関数自体の処理は終わりますが、「戻すボタン」の中のクリックイベント(子)が、「草津温泉(onsen変数)」をギュッと握りしめて離しません。

数分後にボタンが押された時、すでに終わったはずの createWishList の時のデータを引っ張り出して使える。

なぜ「クロージャ」と呼ぶのか?

「Closure(閉鎖・閉じ込める)」という名前の通り、 変数を外から触れないように「閉じ込める」 からです。

もし onsen という変数を関数の外(グローバル)に置いてしまうと、他の温泉を追加した時に名前が上書きされて、どの「戻すボタン」を押しても最後に登録した温泉になってしまいます。

クロージャのおかげで、「それぞれのボタンが、自分専用の温泉名を、自分だけのポケットの中に閉じ込めて持っている」 状態になれる。

今回勉強したUdemyコース

【React18対応】モダンJavaScriptの基礎から始める挫折しないためのReact入門
https://www.udemy.com/course/modern_javascipt_react_beginner/

わかりやすくておすすめです。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?