4日目のゴールと今日やること
4日目のテーマは
「検索 × ソート × フィルタの一覧アプリを“プロ仕様の使いやすさ”に進化させる」
ことです。
1〜3日目で、あなたはすでに
- リアルタイム検索
- 昇順 / 降順ソート
- 複合条件フィルタ(年齢・タグ)
- ページネーション
- state 管理
といった、実務レベルの一覧アプリの基礎を作り上げました。
4日目では、さらに一歩進んで
- デバウンス(検索の負荷軽減)
- 検索キーワードのハイライト表示
- ソートアイコンの切り替え
- state の整理(責務の分離)
といった「UI/UX の質を上げるテクニック」を学びます。
今日のキーワードは
“ユーザーが気持ちよく使える一覧アプリ”
です。
デバウンスで検索の負荷を減らす
なぜデバウンスが必要なのか
リアルタイム検索は便利ですが、
ユーザーが 1 文字入力するたびに applyFilters が走るため、
データ量が増えると処理が重くなります。
そこで使うのが デバウンス(debounce) です。
デバウンスとは?
「入力が止まってから一定時間経ったら処理を実行する」
という仕組みです。
デバウンス関数の例
function debounce(fn, delay = 300) {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
JavaScript検索欄にデバウンスを適用
const handleSearch = debounce((value) => {
updateState({ keyword: value });
}, 300);
searchInput.addEventListener("input", () => {
handleSearch(searchInput.value);
});
JavaScript深掘りポイント
- 入力が止まってから 300ms 後に検索
- 無駄な filter の実行を大幅に削減
- 大量データでもサクサク動く
デバウンスは「検索 UI の必須テクニック」です。
検索キーワードをハイライト表示する
なぜハイライトが必要なのか
検索結果が出ても、
「どこが一致したのか」がわからないとユーザーは迷います。
そこで、検索キーワードを強調表示します。
ハイライト関数
function highlight(text, keyword) {
if (!keyword) return text;
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(escaped, "gi");
return text.replace(regex, (match) => `<mark>${match}</mark>`);
}
JavaScriptrenderList に組み込む
function renderList(arr) {
if (!arr.length) {
listDiv.textContent = "該当するユーザーはいません。";
return;
}
let html = "";
arr.forEach(user => {
const name = highlight(user.name, state.keyword);
html += `<p>${name}(${user.age}歳)</p>`;
});
listDiv.innerHTML = html;
}
JavaScript深掘りポイント
- 正規表現でキーワードを検索
<mark>タグで強調- 大文字小文字を無視して一致
これだけで検索結果が一気に見やすくなります。
ソートアイコンを切り替える
UI のイメージ
<button id="sortAge">年齢 ▲▼</button>
<button id="sortName">名前 ▲▼</button>
state に sortKey と sortOrder を追加
const state = {
keyword: "",
sortKey: null, // "age" or "name"
sortOrder: "asc", // "asc" or "desc"
minAge: null,
maxAge: null,
tags: [],
tagMode: "OR",
page: 1,
perPage: 3
};
JavaScriptソートボタンのロジック
sortAge.addEventListener("click", () => {
const newOrder = state.sortOrder === "asc" ? "desc" : "asc";
updateState({ sortKey: "age", sortOrder: newOrder });
});
sortName.addEventListener("click", () => {
const newOrder = state.sortOrder === "asc" ? "desc" : "asc";
updateState({ sortKey: "name", sortOrder: newOrder });
});
JavaScriptソート処理を applyFilters に統合
if (state.sortKey) {
const key = state.sortKey;
const order = state.sortOrder;
result = [...result].sort((a, b) => {
if (order === "asc") {
return a[key] > b[key] ? 1 : -1;
} else {
return a[key] < b[key] ? 1 : -1;
}
});
}
JavaScript深掘りポイント
- sortKey で「何を基準に並べるか」
- sortOrder で「昇順 / 降順」
- ボタンを押すたびに order を反転
これで「プロっぽいソート UI」が完成します。
state を整理して責務を分離する
なぜ state を整理するのか
機能が増えると state が肥大化し、
「何がどこで使われているのか」がわかりにくくなります。
そこで、state を 3 つに分けます。
検索条件(filters)
const filters = {
keyword: "",
minAge: null,
maxAge: null,
tags: [],
tagMode: "OR"
};
JavaScriptソート条件(sortState)
const sortState = {
key: null,
order: "asc"
};
JavaScriptページ情報(pagination)
const pagination = {
page: 1,
perPage: 3
};
JavaScript深掘りポイント
責務を分けることで、
- applyFilters が読みやすくなる
- 機能追加がしやすくなる
- バグが減る
これが「中級者の設計」です。
applyFilters を“読みやすい流れ”に再構築する
function applyFilters() {
let result = [...users];
result = applyKeywordFilter(result);
result = applyAgeFilter(result);
result = applyTagFilter(result);
result = applySort(result);
result = applyPagination(result);
renderList(result);
}
JavaScript深掘りポイント
1つの巨大な関数ではなく、
小さな関数の組み合わせ にすることで、
- 読みやすい
- テストしやすい
- 修正しやすい
というメリットが生まれます。
4日目のまとめ
今日あなたがやったことを整理すると、こうなります。
- デバウンスで検索の負荷を軽減
- 検索キーワードをハイライト表示
- ソートアイコンの切り替え(昇順 / 降順)
- state を filters / sortState / pagination に分割
- applyFilters を小さな関数の組み合わせに再構築
- UI とロジックの分離をさらに強化
どれも実務で必須のテクニックです。
今日いちばん深く理解してほしいこと
4日目の本質は、
「一覧アプリは、map / filter / sort を“どう組み合わせるか”で UX が決まる」
ということです。
検索
→ 年齢フィルタ
→ タグフィルタ
→ ソート
→ ページネーション
→ ハイライト
→ 表示
この流れを整理し、
state と UI をきれいに分離できたあなたは、
もう「ただ動くアプリ」ではなく
“使っていて気持ちいいアプリ” を作れる段階に来ています。
5日目では、この一覧アプリに
- 保存機能(localStorage)
- 複数条件のプリセット
- 並び替え UI の改善
- データの追加・削除
などを加えて、さらに完成度を高めていきます。


