JavaScript | 2週間で身につく、アプリを作りながら学ぶJavaScriptの基本 - 7日目

JavaScript JavaScript
スポンサーリンク

7日目のゴールとテーマ

7日目のテーマは「名簿アプリに“検索・絞り込み・並び替え”を足して、便利アプリに育てる」です。
6日目で「登録して一覧表示する」まではできました。
今日はそこに、

名前で検索する
年齢で絞り込む
年齢順に並び替える

といった“ちょっと賢い機能”を追加していきます。
ここを通ると、「配列をただ表示する」から「配列を“加工して使う”」感覚が一気に強くなります。


6日目の名簿アプリを軽くおさらいする

どんな構造だったかを言葉で整理する

6日目の名簿アプリは、ざっくりこういう構造でした。

JavaScript側に people という配列がある。
1人分は { name: "山田太郎", age: 25 } のようなオブジェクト。
renderList() 関数が people の中身をもとに画面の一覧を描いている。
「追加」ボタンを押すと、入力値から person を作って people.push(person) し、renderList() を呼んでいた。

今日は、この「people をもとに描画する」という考え方はそのままに、
「people を一時的に加工した“別の配列”を表示する」という方向に広げていきます。


検索・絞り込み・並び替えの考え方

元データは変えずに「別の配列」を作る

まず大事な考え方を先に言葉で押さえます。

検索や絞り込み、並び替えをするとき、
元の people 配列そのものをいじる必要はありません。

「検索結果用の配列」
「絞り込み結果用の配列」
「並び替え済みの配列」

を、その都度「元の people から作る」イメージです。

つまり、

元データ:people(アプリの“真実”)
表示用データ:people を加工して作った一時的な配列

という二段構造で考えます。
この「元はそのまま、表示用だけ変える」という感覚は、かなり重要です。


検索機能を追加する準備(HTML)

名前で検索する入力欄とボタンを足す

まずは HTML に検索用の UI を足します。
index.html の「登録された人一覧」の上あたりに、次のようなブロックを追加します。

<h2>検索</h2>
<div>
  <input type="text" id="search-name-input" placeholder="名前で検索">
  <button id="search-button">検索する</button>
  <button id="clear-search-button">検索をクリア</button>
</div>

<h2>登録された人一覧</h2>
<div id="list-area">
  まだ誰も登録されていません。
</div>
HTML

ここで押さえておきたいのは、
検索用の入力欄に id="search-name-input" を付けていること。
「検索する」ボタンと「検索をクリア」ボタンにも id を付けていること。

JavaScript側でこれらをつかんで、検索機能を実装していきます。


renderList を「表示したい配列」を受け取る形に変える

people 固定ではなく「引数で配列を渡す」

6日目の renderList() は、内部で直接 people を見ていました。
検索結果を表示したいときのために、「どの配列を表示するか」を引数で渡せるように変えます。

function renderList(targetPeople) {
  if (targetPeople.length === 0) {
    listAreaElement.textContent = "該当する人がいません。";
    return;
  }

  let lines = [];

  for (let i = 0; i < targetPeople.length; i = i + 1) {
    let person = targetPeople[i];
    let line = (i + 1) + "人目: 名前: " + person.name + " / 年齢: " + person.age + "歳";
    lines.push(line);
  }

  let text = lines.join("\n");

  listAreaElement.textContent = text;
}
JavaScript

そして、「初期表示」や「追加後の表示」では、こう呼びます。

renderList(people);
JavaScript

ここでの重要ポイントは、「renderList は“どの配列を表示するか”を知らない」ということです。
呼び出し側が「people を表示したいのか」「検索結果を表示したいのか」を決めて、
その配列を渡してあげる形にすることで、関数の再利用性が一気に上がります。


名前で検索する機能を実装する

部分一致で検索してみる

main.js に、検索用の要素をつかむコードと、イベント処理を追加します。

let searchNameInputElement = document.getElementById("search-name-input");
let searchButtonElement = document.getElementById("search-button");
let clearSearchButtonElement = document.getElementById("clear-search-button");
JavaScript

次に、「検索する」ボタンのクリックイベントを書きます。

searchButtonElement.addEventListener("click", function () {
  let keyword = searchNameInputElement.value;

  if (keyword === "") {
    alert("検索したい名前を入力してください。");
    return;
  }

  let result = [];

  for (let i = 0; i < people.length; i = i + 1) {
    let person = people[i];

    if (person.name.includes(keyword)) {
      result.push(person);
    }
  }

  renderList(result);
});
JavaScript

ここで深掘りしたいポイントがいくつかあります。

まず、keyword に検索文字列を入れています。
空文字なら検索しても意味がないので、alert して return しています。

次に、result という空の配列を用意しています。
ここに「条件に合った人だけ」を集めていきます。

for ループで people を1人ずつ見ていき、
person.name.includes(keyword) で「名前に keyword が含まれているか」をチェックしています。
includes は「部分一致検索」をしてくれるメソッドです。

条件を満たした人だけ result.push(person) で結果配列に追加し、
最後に renderList(result) で「検索結果だけ」を表示しています。

ここで大事なのは、「people 自体は一切変えていない」ということです。
元データはそのまま、表示用に result を作っているだけです。


検索をクリアして「全員表示」に戻す

元の people を再び render するだけ

「検索をクリア」ボタンのイベントは、とてもシンプルです。

clearSearchButtonElement.addEventListener("click", function () {
  searchNameInputElement.value = "";
  renderList(people);
});
JavaScript

検索欄を空にして、renderList(people) を呼ぶだけで、
元の「全員表示」に戻ります。

ここで改めて、「画面は常に“どの配列を render しているか”で決まる」という構造が見えてきます。
検索中は result を render、
クリアしたら people を render。

この切り替えだけで、検索機能が成立しています。


年齢で絞り込む機能を考える

「20歳以上だけ」「30歳未満だけ」など

次は、年齢で絞り込む機能を考えてみます。
HTML に、例えば次のような UI を足します。

<h2>年齢で絞り込み</h2>
<div>
  <input type="text" id="min-age-input" placeholder="最小年齢">
  <input type="text" id="max-age-input" placeholder="最大年齢">
  <button id="filter-age-button">年齢で絞り込む</button>
</div>
HTML

JavaScript側で要素をつかみます。

let minAgeInputElement = document.getElementById("min-age-input");
let maxAgeInputElement = document.getElementById("max-age-input");
let filterAgeButtonElement = document.getElementById("filter-age-button");
JavaScript

そして、「年齢で絞り込む」ボタンのイベントを書きます。

filterAgeButtonElement.addEventListener("click", function () {
  let minAgeText = minAgeInputElement.value;
  let maxAgeText = maxAgeInputElement.value;

  let minAge = minAgeText === "" ? null : Number(minAgeText);
  let maxAge = maxAgeText === "" ? null : Number(maxAgeText);

  if ((minAgeText !== "" && Number.isNaN(minAge)) ||
      (maxAgeText !== "" && Number.isNaN(maxAge))) {
    alert("年齢は数字で入力してください。");
    return;
  }

  let result = [];

  for (let i = 0; i < people.length; i = i + 1) {
    let person = people[i];
    let ok = true;

    if (minAge !== null && person.age < minAge) {
      ok = false;
    }

    if (maxAge !== null && person.age > maxAge) {
      ok = false;
    }

    if (ok) {
      result.push(person);
    }
  }

  renderList(result);
});
JavaScript

ここでの重要ポイントを丁寧に見ていきます。

minAgeText / maxAgeText は文字列です。
空文字なら「下限なし」「上限なし」とみなしたいので、
minAgeText === "" ? null : Number(minAgeText); のようにして、
空なら null、そうでなければ数字に変換しています。

入力が数字でない場合は、Number.isNaN でチェックして alert しています。

ループの中では、まず ok = true としておき、
条件に合わない場合にだけ ok = false にしています。

minAge が設定されていて、person.age がそれより小さいなら false。
maxAge が設定されていて、person.age がそれより大きいなら false。

最後に ok が true の人だけ result に追加し、
renderList(result) で表示しています。

ここでのポイントは、「条件が増えても、ok フラグを使うと整理しやすい」ということです。
「この人は条件を満たしているか?」を1つの boolean で表現しているわけです。


並び替え機能を追加する

年齢の昇順・降順で並べる

今度は、「年齢順に並び替える」機能を考えます。
HTML にボタンを足します。

<h2>並び替え</h2>
<div>
  <button id="sort-age-asc-button">年齢が若い順</button>
  <button id="sort-age-desc-button">年齢が高い順</button>
</div>
HTML

JavaScript側で要素をつかみます。

let sortAgeAscButtonElement = document.getElementById("sort-age-asc-button");
let sortAgeDescButtonElement = document.getElementById("sort-age-desc-button");
JavaScript

並び替えには、配列の slicesort を組み合わせるのが定番です。

sortAgeAscButtonElement.addEventListener("click", function () {
  let copied = people.slice();

  copied.sort(function (a, b) {
    return a.age - b.age;
  });

  renderList(copied);
});

sortAgeDescButtonElement.addEventListener("click", function () {
  let copied = people.slice();

  copied.sort(function (a, b) {
    return b.age - a.age;
  });

  renderList(copied);
});
JavaScript

ここでの重要ポイントを深掘りします。

people.slice() は、「people のコピー配列」を作ります。
sort は元の配列を直接並び替えてしまうので、
people を壊さないように、コピーに対して sort をかけています。

copied.sort(function (a, b) { return a.age - b.age; });
これは、「a.age が小さいものを前に、大きいものを後ろに並べる」という意味です。
a.age – b.age が負なら a が先、正なら b が先、0なら同じ、というルールで並び替えます。

降順の場合は、b.age - a.age にするだけです。
これで「年齢が高い順」に並び替えられます。

ここでも、「元データ people はそのまま」「表示用に並び替えた配列を render」という構造は変わりません。


検索・絞り込み・並び替えをどう組み合わせるか

「今どの状態を表示しているか」を意識する

ここまで来ると、少し気になることが出てきます。
「検索したあとに並び替えボタンを押したらどうなるの?」という問題です。

シンプルにやるなら、「ボタンを押すたびに people からやり直す」という方針でも構いません。
つまり、

検索ボタン → people から検索結果を作って render
絞り込みボタン → people から絞り込み結果を作って render
並び替えボタン → people をコピーして sort して render

というように、「毎回 people を起点にする」やり方です。

一方で、「検索結果をさらに並び替えたい」などをやりたくなると、
「今表示している配列」をどこかに持っておく必要が出てきます。

例えば、let currentList = people; のような変数を用意しておき、
検索や絞り込みのたびに currentList を更新し、
renderList(currentList) を呼ぶ、という設計もあります。

7日目の段階では、「まずは全部 people 起点でいい」「そのうち currentList という考え方も出てくる」くらいの理解で十分です。
大事なのは、「状態は配列で表し、画面はその結果でしかない」という筋を見失わないことです。


7日目で一番大事な感覚

「配列を“加工して使う”のがアプリの本番」

今日あなたがやったのは、

配列から条件に合うものだけを集める(検索・絞り込み)
配列を並び替える(sort)
元データはそのままに、表示用の配列をその都度作る

という、「配列をアプリの中でどう料理するか」という部分です。

ここができると、

タスクを「未完了だけ表示」
商品を「価格が安い順に並べる」
メモを「キーワードで検索する」

といった、現実のアプリでよく見る機能が、
自分の手で組み立てられるようになります。


7日目のまとめ

今日のキーポイントを短く整理すると、

renderList を「表示したい配列を引数で受け取る」形に変えた
名前検索で、includes を使って部分一致検索を実装した
検索結果用の配列 result を作り、元の people は変えない方針を体験した
年齢での絞り込みで、「下限・上限を持つ条件」を boolean フラグ(ok)で整理した
並び替えで、slice でコピー → sort で並び替え → renderList という流れを身につけた

次の8日目では、この名簿アプリの経験を土台にしつつ、
「別のテーマのアプリ(例えば簡易タスク管理)」に同じパターンを移植していきます。

もし余裕があれば、
今日のアプリに「名前の完全一致検索」「“さん”を付けて表示」「登録人数を表示」など、
自分なりの小さな機能を足してみてください。
その「こうしたいな」を形にしていく感覚こそが、アプリ開発の一番おもしろいところです。

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