基礎から学ぶJavaScript入門 90日コース | 非同期処理とAPI - Day 65:検索APIアプリ②

JavaScript 90日で身につけるJavaScript
スポンサーリンク
スポンサーリンク

Day 65:検索APIアプリ②

Day 65では、第5フェーズ「非同期処理とAPI」の続きとして、前回作成したユーザー検索アプリに機能追加を行います。今回追加するのは、ローディング、エラー処理、検索結果なし表示の3つです。Day 64では、キーワード入力、API検索、結果表示という検索アプリの基本形を作成しました。そこでは、入力欄からキーワードを受け取り、公開APIから取得したユーザーデータを filter() で絞り込み、結果を一覧表示する流れを学びました。Day 65では、その基本形を「より使いやすく、より実用的なアプリ」へ育てていきます。

初心者の方にとって、このDay 65はとても大切です。なぜなら、ここで学ぶ内容は、見た目の追加機能のように見えて、実はアプリの完成度を大きく左右するからです。検索機能そのものが動くだけでは、まだ実用的とは言えません。たとえば、検索ボタンを押してもすぐに何も変わらなければ、使う人は不安になります。通信に失敗したのに何も案内がなければ、壊れていると思われるかもしれません。検索結果が0件だったときに何も表示されなければ、検索できたのかどうかもわかりません。こうした「成功しなかったとき」や「待っている途中」の体験まで考えることが、アプリ開発ではとても重要です。

つまりDay 65は、「検索機能を作る回」ではなく、「検索アプリを完成に近づける回」です。ここでは、Day 62で学んだローディング処理、Day 63で学んだAPIエラー処理、そしてDay 64で扱った検索結果表示を、1つのアプリの中で整理して統合していきます。初心者の方は、「何を追加するか」だけでなく、「なぜそれが必要なのか」「どのタイミングで処理すべきか」「どんな状態をユーザーへ伝えるべきか」という設計の考え方まで意識しながら進めると、非常に力がつきます。今回も、プログラミング初心者向けに、ステップバイステップで、例題・コード・テンプレートを交えながら丁寧に解説していきます。

まず最初に結論をつかむ

最初に今回の内容を一言でまとめると、Day 65では「検索中であること」「検索に失敗したこと」「検索結果が0件であること」を、画面でわかるようにすることが目標です。

今回追加する機能を整理すると、次の通りです。

  • ローディング:検索中であることを表示する
  • エラー処理:通信失敗時に案内を出す
  • 検索結果なし表示:該当データがないことを伝える

初心者の方は、これらを全部まとめて「状態を伝える機能」と考えるとわかりやすいです。検索アプリでは、結果だけでなく、途中状態や失敗状態も画面に伝える必要があります。

なぜこの3つの機能追加が重要なのか

ここはDay 65の中心です。なぜローディング、エラー処理、検索結果なし表示が大切なのかというと、検索アプリには「成功」以外にもいくつもの状態があるからです。

たとえば、検索アプリには少なくとも次の状態があります。

  • まだ何も検索していない状態
  • 検索中の状態
  • 検索成功で結果がある状態
  • 検索成功だが結果が0件の状態
  • 通信失敗で検索できなかった状態

初心者の方は、アプリは「結果を出すだけのもの」ではなく、「今どんな状態かを伝えるもの」でもあると理解すると、設計の視点が大きく広がります。

Day 64の基本アプリを振り返る

Day 64では、ユーザー検索アプリの基本形を作りました。そこでは、次の流れがありました。

  • 入力欄からキーワードを取得する
  • APIからユーザー一覧を取得する
  • `filter()` で条件に合うユーザーを絞り込む
  • 結果を一覧表示する

この流れ自体はすでに検索アプリとして成立しています。しかし、まだ次のような問題が残っていました。

  • 検索中に何が起きているかわかりにくい
  • 通信に失敗したときの案内が弱い
  • 結果0件のときに表示があいまいになりやすい

Day 65では、この「基本機能はあるが、体験としてはまだ不親切」な状態を改善していきます。

最初に完成イメージを持つ

コードに入る前に、今回の完成イメージを言葉で整理しておきます。理想的には、次のような流れになると使いやすいです。

  • ユーザーがキーワードを入力して検索ボタンを押す
  • すぐに「検索中です…」と表示される
  • 通信が成功したら、結果を一覧表示する
  • 該当ユーザーがいなければ「見つかりませんでした」と表示する
  • 通信に失敗したら「検索に失敗しました」と表示する
  • 検索が終わったらローディング表示を消す

初心者の方は、完成コードを見る前にこの流れを日本語で説明できるようにしておくと、JavaScriptで何を書くべきかが見えやすくなります。

ローディング機能を追加する意味

まずはローディングです。ローディングは、検索ボタンを押してから結果が出るまでの間、「今ちゃんと処理中です」と伝える役割があります。検索アプリでは、押した瞬間に必ず結果が出るとは限りません。API通信には待ち時間があるからです。

そのため、検索開始時に何も変化がないと、ユーザーは次のように感じやすくなります。

  • ボタンが押せていないのではないか
  • 検索機能が壊れているのではないか
  • もう一度押したほうがよいのではないか

ローディング表示は、この不安を減らすための大切な案内です。初心者の方は、ローディングを「飾り」ではなく、「処理中を伝えるUI」と考えると理解しやすいです。

ローディング表示の基本形

まずHTMLにローディング用の要素を置きます。

<p id="loading" style="display: none;">検索中です...</p>

最初は display: none; にして見えない状態にしておきます。そして検索開始時に表示し、終了後に非表示へ戻します。

loading.style.display = "block";
loading.style.display = "none";
JavaScript

初心者の方は、ここで「最初から表示するのではなく、必要なときだけ出す」という考え方を意識するとよいです。

どのタイミングでローディングを出すのか

ここは重要です。ローディング表示は、fetch() の前に出す必要があります。もし取得処理が始まったあとに表示しようとすると、タイミングとして遅くなります。

正しい流れはこうです。

  • 入力チェックを終える
  • 検索開始と同時にローディングを表示する
  • API通信を行う
  • 成功でも失敗でも最後にローディングを消す

初心者の方は、「ローディングは検索が始まる前に出し、検索が完全に終わってから消す」と覚えると整理しやすいです。

エラー処理を追加する意味

次にエラー処理です。検索アプリでは、API通信が失敗することがあります。ネットワークの問題、URLの問題、サーバー側の不調など、原因はいろいろあります。大切なのは、失敗そのものをゼロにすることではなく、「失敗したときにどう見せるか」を設計することです。

エラー処理がないと、ユーザーから見ると「何も表示されない」「止まったように見える」だけになりやすいです。それでは非常に不親切です。初心者の方は、失敗したときこそメッセージ表示が大事だと理解すると、より実用的なアプリづくりに近づきます。

try / catch を検索アプリに組み込む

Day 63で学んだ try / catch を使います。たとえば、次のような形です。

try {
  const response = await fetch("https://jsonplaceholder.typicode.com/users");

  if (!response.ok) {
    throw new Error("サーバーエラーが発生しました。");
  }

  const users = await response.json();
} catch (error) {
  message.textContent = "検索に失敗しました。時間をおいて再試行してください。";
  console.log(error);
}
JavaScript

このようにすると、失敗したときに画面へ案内を出しつつ、開発者向けには詳細をコンソールで確認できます。初心者の方は、「画面に見せるメッセージ」と「自分が確認する情報」は役割が違うと理解すると整理しやすいです。

検索結果なし表示の意味

ここも非常に大事です。検索結果が0件というのは、エラーではありません。通信は成功していて、検索も実行されています。ただ、「条件に合うデータがなかった」というだけです。この状態をきちんと区別して表示することが重要です。

初心者の方は、0件とエラーを同じように考えがちですが、実は意味がまったく違います。

  • 0件:検索は正常にできたが、該当ユーザーがいない
  • エラー:検索処理自体が正常に完了していない

この違いを意識できると、画面メッセージの設計がぐっとよくなります。

検索結果なし表示の基本形

検索結果配列の長さを見て、0件なら専用メッセージを出します。

if (results.length === 0) {
  message.textContent = "該当するユーザーが見つかりませんでした。";
  return;
}
JavaScript

この1行があるだけで、ユーザーは「検索は動いたが結果がなかった」と理解できます。初心者の方は、「何も表示しない」ことは、ユーザーにとって親切ではない場面が多いと覚えておくとよいです。

空欄チェック・0件・エラーは別物

ここはかなり重要なので深掘りします。検索アプリでは、似て見えて役割の違う状態がいくつもあります。

  • 空欄チェック:そもそも検索条件が入力されていない
  • 検索結果0件:条件はあるが該当データがない
  • 通信エラー:検索そのものが正常にできていない

この3つはすべて別です。そのため、メッセージも分けるべきです。たとえば、

  • 空欄なら「キーワードを入力してください。」
  • 0件なら「該当するユーザーが見つかりませんでした。」
  • エラーなら「検索に失敗しました。時間をおいて再試行してください。」

初心者の方は、検索アプリでは「失敗」と一言でまとめず、状態ごとに丁寧に分けることが大切だと理解すると実践的です。

メッセージ表示欄を状態案内として使う

検索アプリでは、メッセージ欄がとても重要です。ここに現在の状態を表示することで、ユーザーは何が起きているかを理解しやすくなります。

たとえばメッセージ欄には、次のような内容が入ります。

  • 検索前:「キーワードを入力して検索してください。」
  • 検索中:「ユーザーを検索中です…」
  • 成功:「3件のユーザーが見つかりました。」
  • 0件:「該当するユーザーが見つかりませんでした。」
  • 空欄:「キーワードを入力してください。」
  • エラー:「検索に失敗しました。時間をおいて再試行してください。」

初心者の方は、メッセージ欄を単なる補助ではなく、「アプリの状態を伝える中心」と考えると設計しやすくなります。

完成コードの流れを先に言葉で整理する

ここで、完成版の流れをもう一度日本語で整理します。

  • ボタンが押されたら入力値を取得する
  • 空欄ならエラーメッセージを出して終了する
  • 前回の結果を消す
  • ローディング表示を出し、検索中メッセージを表示する
  • APIからユーザー一覧を取得する
  • 取得に成功したら `filter()` で絞り込む
  • 0件なら専用メッセージを出す
  • 結果があれば一覧表示する
  • もし通信失敗ならエラーメッセージを出す
  • 最後にローディング表示を消す

初心者の方は、この流れを理解してからコードを見ると、処理の場所ごとの意味がかなり見えやすくなります。

そのまま使える完成テンプレート

ここでは、Day 65の内容をまとめた完成テンプレートを紹介します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Day 65 検索APIアプリ②</title>
  <style>
    /* ローディング表示 */
    #loading {
      display: none;
      color: blue;
      font-weight: bold;
    }

    /* エラーメッセージ用 */
    .error {
      color: red;
    }

    /* 結果リストの余白 */
    li {
      margin-bottom: 8px;
    }
  </style>
</head>
<body>
  <h2>ユーザー検索アプリ</h2>

  <input type="text" id="keywordInput" placeholder="名前を入力してください">
  <button id="searchButton">検索</button>

  <p id="loading">検索中です...</p>
  <p id="message">キーワードを入力して検索してください。</p>
  <ul id="resultList"></ul>

  <script>
    // 必要な要素を取得する
    const keywordInput = document.getElementById("keywordInput");
    const searchButton = document.getElementById("searchButton");
    const loading = document.getElementById("loading");
    const message = document.getElementById("message");
    const resultList = document.getElementById("resultList");

    // ユーザー検索を行う関数
    async function searchUsers() {
      // 入力値を取得する
      const keyword = keywordInput.value.trim();

      // 前回の検索結果を空にする
      resultList.innerHTML = "";

      // メッセージの見た目を初期化する
      message.classList.remove("error");

      // 空欄なら検索しない
      if (keyword === "") {
        message.textContent = "キーワードを入力してください。";
        message.classList.add("error");
        return;
      }

      try {
        // ローディング表示を出す
        loading.style.display = "block";

        // 検索中メッセージを表示する
        message.textContent = "ユーザーを検索中です...";

        // APIからユーザー一覧を取得する
        const response = await fetch("https://jsonplaceholder.typicode.com/users");

        // HTTPエラーなら失敗として扱う
        if (!response.ok) {
          throw new Error("サーバーエラーが発生しました。");
        }

        // JSONデータを読み取る
        const users = await response.json();

        // 名前にキーワードを含むユーザーだけを絞り込む
        const results = users.filter(function (user) {
          return user.name.toLowerCase().includes(keyword.toLowerCase());
        });

        // 検索結果が0件ならメッセージを表示する
        if (results.length === 0) {
          message.textContent = "該当するユーザーが見つかりませんでした。";
          return;
        }

        // 検索成功メッセージ
        message.textContent = `${results.length}件のユーザーが見つかりました。`;

        // 検索結果を画面に表示する
        results.forEach(function (user) {
          const li = document.createElement("li");

          // 名前とメールアドレスを表示する
          li.textContent = `${user.name} / ${user.email}`;

          resultList.appendChild(li);
        });
      } catch (error) {
        // 通信失敗時のメッセージ
        message.textContent = "検索に失敗しました。時間をおいて再試行してください。";
        message.classList.add("error");

        // 開発者向けにエラー詳細を出力する
        console.log("エラー詳細:", error);
      }

      // 成功でも失敗でもローディング表示を消す
      loading.style.display = "none";
    }

    // 検索ボタンが押されたら検索を実行する
    searchButton.addEventListener("click", function () {
      searchUsers();
    });
  </script>
</body>
</html>

この完成コードの重要な見どころ

初心者の方は、完成コードの中で特に次の部分を意識すると理解が深まりやすいです。

  • 空欄チェックが最初にあること
  • 検索開始前に `resultList.innerHTML = “”` で前回結果を消していること
  • ローディング表示を `fetch()` の前に出していること
  • `response.ok` でHTTPエラー確認もしていること
  • 検索結果0件と通信エラーを別メッセージにしていること
  • 最後にローディング表示を必ず消していること

これらは単なる書き方ではなく、「使いやすい検索アプリにするための設計」です。

特に深掘りしたい重要ポイント

今回特に大切なのは、「0件」と「エラー」をきちんと分けて扱っている点です。初心者の方は、どちらも「うまく結果が出なかった」という意味では似て見えるかもしれません。しかし実際には、0件は検索が成功している状態であり、エラーは検索処理自体が正常に完了していない状態です。この違いを正しく扱えるようになると、アプリの状態管理が一段階レベルアップします。

また、ローディング表示を最後に必ず消すことも非常に重要です。途中で return する分岐があるときは特に注意が必要です。初心者の方は、「表示を出す処理」と「戻す処理」は必ずセットで考える習慣を持つと、UI不具合が減りやすくなります。

情報セキュリティの観点での注意

今回の検索アプリでも、外部APIから取得したデータを画面へ表示しています。そのため、表示方法には引き続き注意が必要です。学習段階では信頼できる練習用APIを使う前提ですが、外部データを扱う以上、「そのまま画面へ埋め込む内容」には慎重であるべきです。まずは textContent を使った安全な表示を基本にするとよいです。また、エラー時に内部情報をそのままユーザー画面へ出さないようにすることも大切です。初心者の方は、「便利さ」と「安全さ」を両立する意識を少しずつ持つと、堅実な開発習慣につながります。

初心者が特に注意したいポイント

Day 65では、次の点を特に意識すると理解が深まりやすいです。

  • ローディング表示は検索開始前に出します。
  • 成功でも失敗でも最後にローディングを消します。
  • 検索結果0件と通信エラーは別の状態です。
  • メッセージ欄は現在の状態を伝える重要な場所です。
  • 空欄チェック、0件表示、エラー表示はそれぞれ役割が違います。

特に重要なのは、「ただ検索できる」だけでなく、「どんな状態でもユーザーが迷わない」ようにすることです。

Day 65のゴールを整理する

Day 65では、次のことができるようになっていれば十分です。

  • 検索アプリにローディング表示を追加できる
  • 通信失敗時のエラー処理を実装できる
  • 検索結果0件時に専用メッセージを表示できる
  • 空欄、0件、エラーの違いを説明できる
  • 検索アプリの状態をメッセージ欄で案内できる

ここまでできれば、Day 65としてはとても順調です。検索機能そのものだけでなく、ユーザー体験まで含めて考えられるようになっています。

まとめ

Day 65では、第5フェーズ「非同期処理とAPI」の続きとして、ユーザー検索アプリにローディング、エラー処理、検索結果なし表示を追加しました。これによって、検索アプリは「キーワードを入れて結果を出す」だけでなく、「今検索中であること」「検索に失敗したこと」「該当するデータがなかったこと」まで丁寧に伝えられるようになります。技術的には、ローディング表示の切り替え、try / catch による通信失敗対応、results.length === 0 による0件分岐、メッセージ欄による状態案内など、これまで学んだ要素が統合されています。

特に大切なのは、空欄、0件、エラーがすべて別の状態であり、それぞれに合ったメッセージや処理が必要だという点です。この違いをきちんと扱えるようになると、アプリは単に動くだけでなく、使う人にとってわかりやすく信頼しやすいものへ近づきます。

次に進む前に、今回のテンプレートを実際に動かし、空欄検索、ヒットする検索、0件検索、URLをわざと変えたエラー検索の4パターンを試してみるのがおすすめです。そして、「それぞれの状態で何を表示すべきか」を自分の言葉で説明できるようになると、Day 65の理解がかなり深まっています。

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