2日目のゴールと今日のテーマ
2日目は「実際に“消えないメモ帳”を動く形にする日」です。
1日目で学んだのは、localStorage と JSON の基本的な仕組みでした。今日はそれを使って、
メモを追加できる。
メモを編集できる。
メモを削除できる。
ページをリロードしてもメモが残っている。
というところまで持っていきます。
特に大事なのは、「どのタイミングで localStorage に保存するか」という“保存タイミングの設計”です。
アプリ全体の構成をざっくり決める
画面のイメージを言葉で描く
まずは、どんな画面にするかを言葉で整理します。
上の方に「新規メモ入力欄」と「追加ボタン」がある。
その下に「メモ一覧」が縦に並ぶ。
各メモには「内容」と「編集ボタン」と「削除ボタン」が付いている。
編集ボタンを押すと、そのメモだけ入力欄に変わる。
このくらいのイメージが頭にあれば十分です。
ToDo アプリと同じく、「1 メモ = 1 行」という構造で考えます。
HTML の最小構成を用意する
次のような HTML を用意します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>ローカル保存対応メモ帳</title>
<style>
body { font-family: sans-serif; padding: 20px; background: #f8f9fa; }
.app-title { font-size: 20px; margin-bottom: 16px; }
.memo-input-row { margin-bottom: 16px; }
.memo-input-row textarea { width: 100%; height: 60px; padding: 8px; }
.memo-input-row button { margin-top: 8px; padding: 4px 10px; }
.memos-container { margin-top: 16px; }
.memo-item { border: 1px solid #ddd; background: #fff; padding: 8px; margin-bottom: 8px; }
.memo-text { white-space: pre-wrap; }
.memo-actions { margin-top: 8px; text-align: right; }
.memo-actions button { margin-left: 6px; }
</style>
</head>
<body>
<div class="app">
<div class="app-title">ローカル保存対応メモ帳</div>
<div class="memo-input-row">
<textarea id="new-memo-input" placeholder="メモを入力..."></textarea>
<button id="add-memo-button">追加</button>
</div>
<div id="memos-container" class="memos-container"></div>
</div>
<script src="app.js"></script>
</body>
</html>
ここで押さえておきたいのは、
新規メモ入力欄は <textarea> にして、複数行も書けるようにしていること。
メモ一覧は <div id="memos-container"> の中に JavaScript で差し込むこと。
1 メモは .memo-item という 1 つのブロックとして扱うこと。
この「構造のイメージ」が、後の state 設計ときれいにつながります。
state と localStorage の関係をはっきりさせる
state は「今のメモ一覧」を持つ
app.js で、まずは state と localStorage のキーを定義します。
const STORAGE_KEY = "local-memo-app";
const state = {
memos: [],
editingMemoId: null,
};
JavaScriptここでのポイントは、
memos が「アプリが扱う全メモ」の配列であること。
editingMemoId が「今どのメモを編集しているか」を表す UI 状態であること。
localStorage はあくまで「保存場所」であって、
アプリの“今”は state に持たせる、という考え方が大事です。
保存と読み込みの関数を先に作る
まず、「保存」と「読み込み」を担当する関数を用意します。
function saveMemosToStorage() {
const json = JSON.stringify(state.memos);
localStorage.setItem(STORAGE_KEY, json);
}
function loadMemosFromStorage() {
const json = localStorage.getItem(STORAGE_KEY);
if (!json) {
state.memos = [];
return;
}
try {
const memos = JSON.parse(json);
if (Array.isArray(memos)) {
state.memos = memos;
} else {
state.memos = [];
}
} catch (e) {
console.error("メモの読み込みに失敗しました", e);
state.memos = [];
}
}
JavaScriptここで深掘りしたいのは、
JSON.parse は失敗する可能性があるので try-catch で守っていること。
localStorage の中身が壊れていても、アプリが落ちないようにしていること。
これが「永続化設計」の大事な視点です。
「保存されているデータは、必ずしもきれいとは限らない」という前提で設計します。
メモ一覧の描画ロジックを作る
render の基本構造を作る
ToDo アプリと同じように、render 関数を用意します。
const memosContainerEl = document.getElementById("memos-container");
function render() {
memosContainerEl.innerHTML = "";
if (state.memos.length === 0) {
const emptyEl = document.createElement("div");
emptyEl.textContent = "メモはまだありません。最初のメモを書いてみましょう。";
emptyEl.style.color = "#777";
emptyEl.style.fontSize = "12px";
memosContainerEl.appendChild(emptyEl);
return;
}
state.memos.forEach((memo) => {
const memoEl = renderMemoItem(memo);
memosContainerEl.appendChild(memoEl);
});
}
JavaScriptここでのポイントは、
メモが 0 件のときに「何もない」ではなく、メッセージを出していること。
1 件のメモの描画を renderMemoItem に分けていること。
「1 関数 1 役割」に近づけると、読みやすさが一気に上がります。
1 件のメモを描画する関数を作る
function renderMemoItem(memo) {
const itemEl = document.createElement("div");
itemEl.className = "memo-item";
const isEditing = state.editingMemoId === memo.id;
if (isEditing) {
renderMemoItemEditing(itemEl, memo);
} else {
renderMemoItemDisplay(itemEl, memo);
}
return itemEl;
}
JavaScriptここで、「表示モード」と「編集モード」を分ける設計にしています。
これは ToDo の編集機能と同じパターンです。
メモの追加機能を実装する
新規メモ入力欄とボタンを取得する
const newMemoInputEl = document.getElementById("new-memo-input");
const addMemoButtonEl = document.getElementById("add-memo-button");
JavaScriptメモ追加のロジックを作る
function addMemo(text) {
const trimmed = text.trim();
if (!trimmed) return;
const newMemo = {
id: Date.now(),
text: trimmed,
};
state.memos.push(newMemo);
saveMemosToStorage();
render();
}
JavaScriptここでの重要ポイントは、
id に Date.now() を使って、一意な値を作っていること。
state.memos を更新したら、必ず saveMemosToStorage と render を呼んでいること。
「状態を変えたら保存と再描画」というセットを徹底することで、
画面と保存内容のズレを防ぎます。
ボタンと Enter キーにイベントを付ける
addMemoButtonEl.addEventListener("click", () => {
addMemo(newMemoInputEl.value);
newMemoInputEl.value = "";
});
newMemoInputEl.addEventListener("keydown", (event) => {
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
addMemo(newMemoInputEl.value);
newMemoInputEl.value = "";
}
});
JavaScriptここでは、
通常の Enter は改行として使い、
Ctrl+Enter(または Cmd+Enter)で追加、という UX にしています。
「テキストエリアで Enter を押したら改行したい」という自然な動きと、
「ショートカットで追加したい」という両方を満たす設計です。
メモの表示モードと編集モードを実装する
表示モードの描画
function renderMemoItemDisplay(itemEl, memo) {
const textEl = document.createElement("div");
textEl.className = "memo-text";
textEl.textContent = memo.text;
const actionsEl = document.createElement("div");
actionsEl.className = "memo-actions";
const editButton = document.createElement("button");
editButton.textContent = "編集";
editButton.addEventListener("click", () => {
startEditMemo(memo.id);
});
const deleteButton = document.createElement("button");
deleteButton.textContent = "削除";
deleteButton.addEventListener("click", () => {
deleteMemo(memo.id);
});
actionsEl.appendChild(editButton);
actionsEl.appendChild(deleteButton);
itemEl.appendChild(textEl);
itemEl.appendChild(actionsEl);
}
JavaScriptここでのポイントは、
編集ボタンと削除ボタンに、それぞれ memo.id を渡していること。
「どのメモを操作するか」を id で特定する設計にしていること。
これは ToDo アプリとまったく同じ考え方です。
編集モードの描画
function renderMemoItemEditing(itemEl, memo) {
const textareaEl = document.createElement("textarea");
textareaEl.value = memo.text;
textareaEl.style.width = "100%";
textareaEl.style.height = "80px";
const actionsEl = document.createElement("div");
actionsEl.className = "memo-actions";
const saveButton = document.createElement("button");
saveButton.textContent = "保存";
const cancelButton = document.createElement("button");
cancelButton.textContent = "キャンセル";
saveButton.addEventListener("click", () => {
updateMemoText(memo.id, textareaEl.value);
});
cancelButton.addEventListener("click", () => {
finishEditMemo();
});
textareaEl.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
finishEditMemo();
} else if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
updateMemoText(memo.id, textareaEl.value);
}
});
actionsEl.appendChild(saveButton);
actionsEl.appendChild(cancelButton);
itemEl.appendChild(textareaEl);
itemEl.appendChild(actionsEl);
setTimeout(() => {
textareaEl.focus();
textareaEl.select();
}, 0);
}
JavaScriptここで深掘りしたいのは、
編集モードでは「表示用の div」ではなく「textarea」を使っていること。
保存・キャンセル・Esc・Ctrl+Enter で自然な編集体験を作っていること。
setTimeout で描画後にフォーカスを当てていること。
特に最後のフォーカスは、
「編集ボタンを押したらすぐ書き始められる」という UX にとても効きます。
メモの編集・削除ロジックと保存タイミング
編集開始と終了の state 操作
function startEditMemo(id) {
state.editingMemoId = id;
render();
}
function finishEditMemo() {
state.editingMemoId = null;
render();
}
JavaScriptここでの重要ポイントは、
「今どのメモが編集モードか」も state の一部として扱っていること。
描画側は state.editingMemoId を見て、「自分が編集モードかどうか」を判断していること。
モードを state で管理するのは、設計としてとても強いパターンです。
メモ内容の更新と保存
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ここでの深掘りポイントは、
空文字は許可せず、そのままキャンセル扱いにしていること。
配列の更新を「map で新しい配列を作る」形にしていること。
更新後に saveMemosToStorage と finishEditMemo(→ render)を呼んでいること。
「状態を変えたら保存と再描画」という流れを、
追加だけでなく編集にも徹底しています。
メモ削除と保存
function deleteMemo(id) {
state.memos = state.memos.filter((memo) => memo.id !== id);
saveMemosToStorage();
render();
}
JavaScript削除も同じく、
配列を直接いじるのではなく、filter で「残すものだけ」を残した新しい配列を作っていること。
削除後に saveMemosToStorage と render を呼んでいること。
これで、「画面に見えているもの」と「保存されているもの」が常に一致します。
2日目のまとめと、明日へのつなぎ
2日目であなたがやったのは、「localStorage を使った“消えないメモ帳”のコア部分」です。
state.memos を「アプリの真実」として持ち、localStorage は保存場所として扱ったこと。
JSON.stringify / JSON.parse を使って、配列を文字列として保存・復元したこと。
メモ追加・編集・削除のたびに saveMemosToStorage を呼ぶ「保存タイミングの設計」をしたこと。
編集モードを editingMemoId で管理し、表示モードと編集モードを切り替える設計にしたこと。
明日(3日目)はここから、
保存タイミングをもう少し工夫する(入力中に自動保存するか、しないか)。
「保存中」「保存済み」などのステータス表示を設計してみる。
localStorage のデータ構造を少しだけ拡張してみる。
といった、「永続化設計を一段深くする」方向に進めていきます。
もし余力があれば、2日目のうちに、
メモをいくつか追加して、ページをリロードしても残っているか確かめる。
localStorage の中身(Application タブ)を見て、JSON がどう保存されているか眺めてみる。
という“小さな確認”をしてみてください。
「ブラウザの中に、自分のメモがちゃんと残っている」という実感が、
永続化の感覚をぐっとリアルなものにしてくれます。


