JavaScript | 1 日 120 分 × 7 日アプリ学習:ローカル保存対応メモ帳

APP JavaScript
スポンサーリンク

3日目のゴールと今日のテーマ

3日目は「永続化の“質”を上げる日」です。
昨日までで、メモは追加・編集・削除できて、ページを再読み込みしても残るようになりました。
今日はそこから一歩進めて、

保存タイミングをどう設計するか。
localStorage への書き込みをどう“丁寧に扱うか”。
「今保存されたのか?」をユーザーにどう伝えるか。

このあたりを、実際のコードと一緒に深掘りしていきます。


保存タイミングの設計を言葉で整理する

いつ保存するか、3つのパターンを考える

メモ帳で「いつ localStorage に保存するか」は、実は設計の肝です。
代表的なパターンは次の三つです。

入力が終わったタイミングで保存する(追加ボタン、保存ボタンを押したとき)。
入力中に自動保存する(一定時間ごと、または入力のたび)。
ページを離れる直前にまとめて保存する。

2日目までの実装は「1. 操作のたびに保存する」でした。
これはシンプルで分かりやすく、初心者にも扱いやすい設計です。
3日目では、この「操作のたび保存」をベースにしつつ、
「自動保存」や「保存ステータス表示」をどう設計するかを考えていきます。

なぜ「なんでもかんでも即保存」がベストとは限らないのか

localStorage はとても便利ですが、
書き込みは「無料」ではありません。
大量のデータを、短い間隔で何度も書き込むと、

パフォーマンスが落ちる。
ブラウザが一瞬固まるように感じることがある。

といった問題が出てきます。

メモ帳のようなアプリでは、
「1 文字入力するたびに保存」はやりすぎです。
だからこそ、「どのタイミングで保存するか」を設計として考える価値があります。


現状の保存フローをもう一度整理する

今の保存フローをコードで確認する

2日目までの設計では、保存はこうでした。

メモ追加時:

function addMemo(text) {
  const trimmed = text.trim();
  if (!trimmed) return;

  const newMemo = {
    id: Date.now(),
    text: trimmed,
  };

  state.memos.push(newMemo);
  saveMemosToStorage();
  render();
}
JavaScript

メモ編集時:

function updateMemoText(id, newText) {
  const trimmed = newText.trim();
  if (!trimmed) {
    finishEditMemo();
    return;
  }

  state.memos = state.memos.map((memo) => {
    if (memo.id !== id) return memo;
    return { ...memo, text: trimmed };
  });

  saveMemosToStorage();
  finishEditMemo();
}
JavaScript

削除時:

function deleteMemo(id) {
  state.memos = state.memos.filter((memo) => memo.id !== id);
  saveMemosToStorage();
  render();
}
JavaScript

ここでの重要ポイントは、

state を変えた直後に saveMemosToStorage を呼んでいること。
保存と画面更新(render)がセットになっていること。

この設計は「シンプルで壊れにくい」という意味で、とても良いです。
3日目では、ここに「保存ステータス」と「自動保存のアイデア」を足していきます。


保存ステータスを state に組み込む

「保存中」「保存済み」を state で管理する

ユーザーにとっては、「今のメモがちゃんと保存されたか」が気になります。
そこで、「保存状態」を state に持たせてみます。

const state = {
  memos: [],
  editingMemoId: null,
  saveStatus: "idle", // "idle" | "saving" | "saved"
};
JavaScript

ここでのポイントは、

saveStatus も UI 状態の一つだということ。
「今どういう状態か」を画面に出すための情報を、state に持たせていること。

この saveStatus を、画面のどこかに表示します。

保存ステータスの表示エリアを追加する

HTML にステータス表示用の要素を足します。

<div id="save-status" style="font-size: 12px; color: #666; margin-top: 4px;"></div>

app.js で要素を取得します。

const saveStatusEl = document.getElementById("save-status");
JavaScript

render の中、または別関数でステータスを反映します。

function renderSaveStatus() {
  if (state.saveStatus === "idle") {
    saveStatusEl.textContent = "";
  } else if (state.saveStatus === "saving") {
    saveStatusEl.textContent = "保存中...";
  } else if (state.saveStatus === "saved") {
    saveStatusEl.textContent = "保存しました";
  }
}
JavaScript

そして、render の最後で呼びます。

function render() {
  // メモ一覧の描画
  // ...
  renderSaveStatus();
}
JavaScript

ここでの深掘りポイントは、

「保存状態」も state に持たせて、画面はそれを“読むだけ”にしていること。
「状態 → 画面」という一方向の流れを、保存ステータスにも適用していること。

ToDo アプリでやった設計パターンを、そのままここにも使っています。


保存処理に「saving → saved」の流れをつける

saveMemosToStorage の中で saveStatus を更新する

今の saveMemosToStorage はこうでした。

function saveMemosToStorage() {
  const json = JSON.stringify(state.memos);
  localStorage.setItem(STORAGE_KEY, json);
}
JavaScript

ここに、保存状態の更新を足します。

function saveMemosToStorage() {
  state.saveStatus = "saving";
  renderSaveStatus();

  const json = JSON.stringify(state.memos);
  localStorage.setItem(STORAGE_KEY, json);

  state.saveStatus = "saved";
  renderSaveStatus();

  setTimeout(() => {
    if (state.saveStatus === "saved") {
      state.saveStatus = "idle";
      renderSaveStatus();
    }
  }, 1500);
}
JavaScript

ここでやっていることは、

保存前に “saving” にする。
保存が終わったら “saved” にする。
1.5 秒後に “idle” に戻す。

という流れです。

ここでの重要ポイントは、

保存ステータスの変化を「ユーザーに見える形」にしていること。
保存処理の中で state を更新し、その変化を renderSaveStatus が受け取っていること。

これだけで、「ちゃんと保存されている感」がぐっと増します。


自動保存のアイデアと「やりすぎない設計」

編集中に「入力のたび保存」はやりすぎ

例えば、編集モードの textarea の keydown で、
毎回 updateMemoText を呼んで保存する、という設計もできます。

しかし、これは

入力のたびに JSON.stringify と localStorage.setItem が走る。
長文を打っているときに、体感として重くなることがある。

というデメリットがあります。

だからこそ、「いつ保存するか」を慎重に選ぶ必要があります。

デバウンスという考え方(軽くイメージだけ)

中級編として、少しだけ「デバウンス」という考え方に触れておきます。

「最後の入力から 500ms 経ったら保存する」
「連続で呼ばれても、最後の 1 回だけ実行する」

といった動きを作るテクニックです。

例えば、こんな関数を用意します。

function createDebouncedSave(delay) {
  let timerId = null;

  return function debouncedSave() {
    if (timerId !== null) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(() => {
      saveMemosToStorage();
    }, delay);
  };
}

const debouncedSaveMemos = createDebouncedSave(500);
JavaScript

そして、編集中に「即保存」ではなく「遅延保存」を呼びます。

function updateMemoTextLive(id, newText) {
  state.memos = state.memos.map((memo) => {
    if (memo.id !== id) return memo;
    return { ...memo, text: newText };
  });

  debouncedSaveMemos();
  render();
}
JavaScript

ここでの深掘りポイントは、

「入力のたびに保存」ではなく「入力が落ち着いたタイミングで保存」していること。
localStorage への書き込み回数を減らしつつ、内容はちゃんと最新に保っていること。

今日はここまでを「アイデア」として知っておけば十分です。
実装は、余裕が出てきたときにチャレンジしてみるくらいで大丈夫です。


3日目のまとめと、明日へのつなぎ

3日目であなたがやったのは、「永続化の質を上げる設計」です。

保存タイミングを「操作のたび保存」として整理し、その意味を理解したこと。
保存ステータス(idle / saving / saved)を state に持たせ、画面に表示する設計にしたこと。
saveMemosToStorage の中で「saving → saved → idle」という流れを作ったこと。
自動保存のアイデアとして、「入力のたび保存」と「デバウンス保存」の違いをイメージできたこと。

明日(4日目)はここから、

メモの並び順(新しい順/古い順)を設計してみる。
メモにタイトルや更新日時を足して、データ構造を少しリッチにする。
localStorage に保存する「形」を意識して設計してみる。

といった方向に進めていきます。

もし余力があれば、今日は

保存ステータス表示を実際に追加して、「保存しました」が出る瞬間を眺めてみる。
わざと大量のメモを作って、保存が重くならないか体感してみる。

みたいな“小さな実験”をしてみてください。
「永続化は動けばいい」から「どう動くと気持ちいいか」へ——
そこに、設計の面白さが詰まっています。

タイトルとURLをコピーしました