JavaScript | 1 日 120 分 × 7 日アプリ学習:検索 & ソート機能付き一覧

APP JavaScript
スポンサーリンク

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>`);
}
JavaScript

renderList に組み込む

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 の改善
  • データの追加・削除

などを加えて、さらに完成度を高めていきます。

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