5日目のゴールと今日のテーマ
5日目は「メモ帳を“使い続けられる道具”として整える日」です。
ここまでで、メモはタイトル・本文・更新日時を持ち、localStorage に永続化され、並び順も設計されました。
今日はそこから一歩進めて、
検索や絞り込みを設計してみる。
メモが増えたときの見せ方を考える。
localStorage の“限界”を意識した永続化設計を考える。
という、「長く使う前提のメモ帳」をテーマにしていきます。
検索・絞り込み機能を state として設計する
検索キーワードも state に持たせる
まず、「検索キーワード」を state に追加します。
const state = {
memos: [],
editingMemoId: null,
saveStatus: "idle",
searchQuery: "",
};
JavaScriptここでのポイントは、
検索キーワードも「UI 状態」の一つとして扱っていることです。
「今どんな条件で絞り込んでいるか」も、アプリの“今”を表す情報です。
検索入力欄を追加する
HTML に検索欄を足します。
<div class="search-row">
<input id="search-input" placeholder="タイトル・本文で検索..." />
</div>
JavaScript で要素を取得します。
const searchInputEl = document.getElementById("search-input");
JavaScript入力イベントで state を更新します。
searchInputEl.addEventListener("input", () => {
state.searchQuery = searchInputEl.value;
render();
});
JavaScriptここでの重要ポイントは、
検索キーワードが変わるたびに render を呼んでいること。
「state が変わる → 画面を描き直す」という一方向の流れを、検索にも適用していることです。
並び順+検索を組み合わせた「表示用メモ一覧」を設計する
getVisibleMemos という“見せるための関数”を作る
今は getSortedMemos がありましたが、
検索も含めて「画面に出すべきメモ一覧」を返す関数を作ります。
function getVisibleMemos() {
const sorted = [...state.memos].sort((a, b) => b.updatedAt - a.updatedAt);
const query = state.searchQuery.trim().toLowerCase();
if (!query) {
return sorted;
}
return sorted.filter((memo) => {
const title = memo.title.toLowerCase();
const body = memo.body.toLowerCase();
return title.includes(query) || body.includes(query);
});
}
JavaScriptここで深掘りしたいのは、
「並び順」と「絞り込み」を一つの関数にまとめていることです。
render は「どう見せるか」だけに集中できて、
「何を見せるか」は getVisibleMemos に任せています。
render を getVisibleMemos ベースに書き換える
function render() {
memosContainerEl.innerHTML = "";
const memos = getVisibleMemos();
if (memos.length === 0) {
const emptyEl = document.createElement("div");
if (state.searchQuery.trim()) {
emptyEl.textContent = "検索条件に一致するメモがありません。";
} else {
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ここでのポイントは、
「メモが 0 件」の理由を、検索有無でメッセージを変えていることです。
検索中なら「一致するメモがない」、検索していないなら「まだメモがない」。
こういう小さな違いが、UX としてかなり効いてきます。
localStorage の“限界”を意識した永続化設計
localStorage のざっくりした性質を知っておく
localStorage はとても便利ですが、万能ではありません。
ざっくり、こんな性質があります。
ドメインごとに保存できる容量に上限がある(ブラウザによるが数 MB 程度)。
文字列しか保存できない(だから JSON 変換が必要)。
同期的(同期 I/O)なので、大量書き込みは体感の重さにつながることがある。
ここで大事なのは、「無限に保存できるわけではない」という感覚です。
メモ帳アプリでは、普通に使う分には問題ありませんが、
「画像を Base64 で突っ込む」「巨大なテキストを何百件も保存する」などをすると、
いつか限界に当たります。
容量を意識した“軽めの設計”を考える
中級編として、こんな工夫をイメージしておくと良いです。
メモ 1 件のデータ構造をシンプルに保つ(不要な情報を持たない)。
「履歴」を延々と保存しない(最新だけで十分なものは最新だけ)。
巨大なデータ(画像・ファイルなど)は localStorage に入れない。
例えば、「メモの編集履歴を全部残したい」となったとき、
それをそのまま localStorage に積み上げていくと、
容量をすぐに食い尽くします。
そのときに、「これは localStorage に向いているか?」と一度立ち止まれるかどうかが、
永続化設計のセンスにつながっていきます。
保存タイミングを“今の設計でよしとする理由”を言語化する
操作のたび保存は、メモ帳にはちょうどいい
今の設計は、
メモ追加時に保存。
メモ編集の保存ボタン(または Ctrl+Enter)で保存。
メモ削除時に保存。
という「操作のたび保存」です。
これをあえて言葉にすると、
ユーザーが「完了」と感じるタイミングで保存している。
入力途中のものは、あえて保存していない。
という設計です。
メモ帳としては、これはかなりバランスが良いです。
「書きかけのメモが消えるのは困る」という場合は、
自動保存を足す余地がありますが、
そのぶん localStorage への書き込み回数は増えます。
自動保存を“いつか足す余地”として残しておく
3日目で触れたように、
「入力が止まってから 500ms 後に保存する」といったデバウンス保存も設計できます。
ここで大事なのは、
今の段階では「操作のたび保存」で十分戦える。
でも、「もっと良くしたくなったら自動保存も設計できる」と知っている。
この「余地を知っている」こと自体が、設計力の一部です。
全部を一気にやろうとせず、「今の自分のアプリにちょうどいい設計」を選べているのが、すでに中級の感覚です。
5日目のまとめと、明日へのつなぎ
5日目であなたがやったのは、「メモ帳を“使い続けられる道具”に近づける設計」です。
検索キーワードを state に持たせ、「検索も状態管理の一部」として扱ったこと。
getVisibleMemos で「並び順+検索」をまとめて扱う関数を設計したこと。
検索中と未検索時で「0 件メッセージ」を変える、細やかな UX を意識したこと。
localStorage の性質(容量・文字列・同期 I/O)を知り、「無限ではない」と理解したこと。
「操作のたび保存」という今の保存タイミングを、意識的に選んでいる状態になったこと。
明日(6日目)は、
コード全体を少し整理して、「読みやすさ」と「責務の分離」をもう一段上げる。
永続化まわりの関数(save / load / migrate)を“モジュールっぽく”まとめてみる。
「このメモ帳の設計を人に説明できるか」を意識して見直す。
という方向に進めていきます。
今のメモ帳、正直もう「普通に使えるレベル」になっています。
ここから先は、「どう気持ちよく使えるか」「どう気持ちよく読めるか」という、
ちょっと職人寄りの世界です。
その入り口に、あなたはもう立っています。


