4日目のゴールと今日のテーマ
4日目は「メモ帳の“中身の設計”を一段大人にする日」です。
ここまでで、メモは追加・編集・削除できて、localStorage にも保存され、ページを再読み込みしても残るようになりました。
今日はそこから一歩進めて、
メモのデータ構造を少しリッチにする(タイトル・本文・更新日時など)。
並び順(新しい順・古い順)を意識した設計にする。
localStorage に保存する“形”を意識して設計する。
という、「永続化されるデータの設計」にフォーカスしていきます。
メモのデータ構造を“ただの text”から卒業させる
1 件のメモを「タイトル+本文+更新日時」で考える
今までは、メモ 1 件をこう持っていました。
const memo = {
id: 1,
text: "今日のアイデアメモ",
};
JavaScriptこれを、少しだけリッチにします。
const memo = {
id: 1,
title: "今日のアイデア",
body: "・新しいサービス案\n・UI のラフ\n・やることメモ",
updatedAt: Date.now(),
};
JavaScriptここでのポイントは、役割を分けていることです。
タイトルは一覧でパッと見て内容を思い出すための短いテキスト。
本文は実際のメモ内容(複数行 OK)。
updatedAt は「いつ最後に触ったか」を表す数値(タイムスタンプ)です。
この形にしておくと、後で「更新日時順に並べる」「タイトルだけ一覧に出す」などの拡張がしやすくなります。
state の構造を更新する
state をこうしておきます。
const state = {
memos: [
// { id, title, body, updatedAt }
],
editingMemoId: null,
saveStatus: "idle",
};
JavaScriptここで大事なのは、「localStorage に保存されるのは、この memos の中身そのもの」という意識です。
つまり、「永続化されるデータ構造 = state.memos の 1 件の形」と考えて設計していきます。
localStorage に保存される“形”を意識する
JSON にしたときの姿をイメージする
例えば、memos が 2 件あるとき、JSON.stringify するとこうなります。
const memos = [
{
id: 1,
title: "今日のアイデア",
body: "メモ本文...",
updatedAt: 1736400000000,
},
{
id: 2,
title: "買い物リスト",
body: "牛乳\nパン\n卵",
updatedAt: 1736401000000,
},
];
const json = JSON.stringify(memos);
JavaScriptjson の中身は、ざっくりこんな文字列です。
[
{"id":1,"title":"今日のアイデア","body":"メモ本文...","updatedAt":1736400000000},
{"id":2,"title":"買い物リスト","body":"牛乳\nパン\n卵","updatedAt":1736401000000}
]
ここで意識してほしいのは、「localStorage に保存されるのはこの文字列」ということです。
つまり、「将来このデータをどう使いたいか」を考えながら、プロパティ名や構造を決めるのが“永続化設計”です。
読み込み時に「足りないプロパティ」を補う
既に保存済みのデータは、まだ title や updatedAt を持っていないかもしれません。
そこで、読み込み時に「足りないものを補う」処理を入れておきます。
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 = [];
return;
}
state.memos = memos.map((memo, index) => {
return {
id: memo.id ?? Date.now() + index,
title: memo.title ?? "無題のメモ",
body: memo.body ?? memo.text ?? "",
updatedAt: memo.updatedAt ?? Date.now(),
};
});
} catch (e) {
console.error("メモの読み込みに失敗しました", e);
state.memos = [];
}
}
JavaScriptここでの重要ポイントは、「古い形式のデータも受け入れて、新しい形に変換している」ことです。
永続化されたデータは、アプリのバージョンアップをまたいで生き続けるので、「古いデータをどう扱うか」も設計の一部になります。
メモ追加・編集を「タイトル+本文」対応にする
新規メモ入力欄をタイトル+本文に分ける
HTML を少し変えます。
<div class="memo-input-row">
<input id="new-memo-title" placeholder="タイトル(任意)" />
<textarea id="new-memo-body" placeholder="メモ本文を入力..."></textarea>
<button id="add-memo-button">追加</button>
</div>
JavaScript 側で要素を取得します。
const newMemoTitleEl = document.getElementById("new-memo-title");
const newMemoBodyEl = document.getElementById("new-memo-body");
JavaScriptメモ追加ロジックを更新する
function addMemo(title, body) {
const trimmedTitle = title.trim();
const trimmedBody = body.trim();
if (!trimmedBody && !trimmedTitle) {
return;
}
const now = Date.now();
const newMemo = {
id: now,
title: trimmedTitle || "無題のメモ",
body: trimmedBody,
updatedAt: now,
};
state.memos.unshift(newMemo);
saveMemosToStorage();
render();
}
JavaScriptここでの深掘りポイントは、いくつかあります。
タイトルも本文も空なら何もしない、というバリデーションを入れていること。
タイトルが空なら「無題のメモ」と自動で補っていること。
新しいメモを配列の先頭に unshift して、「新しい順」にしていること。
updatedAt に作成時刻を入れていること。
「新しい順に並べる」というのは、メモ帳としてかなり自然な UX です。
この時点で、並び順の設計も一歩進んでいます。
並び順の設計:updatedAt を使ってソートする
render 前に「並び順」を決める
今は unshift で「新しいものが先頭」に来るようにしていますが、
より明示的に「updatedAt の降順で並べる」という設計にしてみます。
function getSortedMemos() {
return [...state.memos].sort((a, b) => b.updatedAt - a.updatedAt);
}
JavaScriptそして、render でこれを使います。
function render() {
memosContainerEl.innerHTML = "";
const memos = getSortedMemos();
if (memos.length === 0) {
const emptyEl = document.createElement("div");
emptyEl.textContent = "メモはまだありません。最初のメモを書いてみましょう。";
emptyEl.style.color = "#777";
emptyEl.style.fontSize = "12px";
memosContainerEl.appendChild(emptyEl);
renderSaveStatus();
return;
}
memos.forEach((memo) => {
const memoEl = renderMemoItem(memo);
memosContainerEl.appendChild(memoEl);
});
renderSaveStatus();
}
JavaScriptここでの重要ポイントは、「並び順のロジックを getSortedMemos に閉じ込めている」ことです。
これにより、「並び順を変えたい」と思ったときに、そこだけ触れば済みます。
編集時にも updatedAt を更新する
編集時の updateMemoText を、タイトル+本文対応にします。
function updateMemo(id, newTitle, newBody) {
const trimmedTitle = newTitle.trim();
const trimmedBody = newBody.trim();
if (!trimmedTitle && !trimmedBody) {
finishEditMemo();
return;
}
const now = Date.now();
state.memos = state.memos.map((memo) => {
if (memo.id !== id) return memo;
return {
...memo,
title: trimmedTitle || "無題のメモ",
body: trimmedBody,
updatedAt: now,
};
});
saveMemosToStorage();
finishEditMemo();
}
JavaScriptこれで、「編集したメモは updatedAt が更新される」ようになります。
getSortedMemos は updatedAt でソートしているので、
編集したメモが一覧の上の方に上がってくる、という自然な挙動になります。
編集モードの UI をタイトル+本文対応にする
編集モードの描画を更新する
function renderMemoItemEditing(itemEl, memo) {
const titleInputEl = document.createElement("input");
titleInputEl.value = memo.title;
titleInputEl.style.width = "100%";
titleInputEl.placeholder = "タイトル(任意)";
const bodyTextareaEl = document.createElement("textarea");
bodyTextareaEl.value = memo.body;
bodyTextareaEl.style.width = "100%";
bodyTextareaEl.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", () => {
updateMemo(memo.id, titleInputEl.value, bodyTextareaEl.value);
});
cancelButton.addEventListener("click", () => {
finishEditMemo();
});
bodyTextareaEl.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
finishEditMemo();
} else if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
updateMemo(memo.id, titleInputEl.value, bodyTextareaEl.value);
}
});
actionsEl.appendChild(saveButton);
actionsEl.appendChild(cancelButton);
itemEl.appendChild(titleInputEl);
itemEl.appendChild(bodyTextareaEl);
itemEl.appendChild(actionsEl);
setTimeout(() => {
bodyTextareaEl.focus();
bodyTextareaEl.select();
}, 0);
}
JavaScriptここでのポイントは、
タイトルと本文を別々の入力欄として扱っていること。
保存時に両方の値を updateMemo に渡していること。
Esc や Ctrl+Enter の UX はそのまま活かしていること。
「データ構造をリッチにしたら、UI もそれに合わせて素直に分解する」
という感覚が、設計としてとても大事です。
4日目のまとめと、明日へのつなぎ
4日目であなたがやったのは、「永続化されるデータの設計を一段引き上げる」ことでした。
メモ 1 件を「タイトル+本文+更新日時」という形で設計し直したこと。
state.memos の 1 件の形を「localStorage に保存されるデータ構造」として意識したこと。
読み込み時に古い形式のデータを新しい形に変換する処理を入れたこと。
updatedAt を使って「新しい順」に並べる設計にしたこと。
編集時に updatedAt を更新し、「最近触ったメモが上に来る」自然な挙動を作ったこと。
明日(5日目)はここから、
検索やフィルタ(タイトルで絞り込みなど)を設計してみる。
メモ数が増えたときの見せ方(折りたたみ、要約表示など)を考える。
localStorage の容量や限界を意識した設計の話を少しだけする。
という方向に進めていきます。
もし余力があれば、今日は
実際にタイトル付きのメモをいくつか作って、編集して、並び順がどう変わるかを眺めてみてください。
「データ構造を変えると、アプリの性格が変わる」という感覚が、かなりはっきり見えてくるはずです。


