JavaScript | 1 日 120 分 × 7 日アプリ学習:API通信アプリ(WeatherAPI.com)

APP JavaScript
スポンサーリンク

4日目のゴールと今日やること

4日目のテーマは
「API 通信の“中身”はそのままに、UI と使い勝手を一段レベルアップさせる」
ことです。

1〜3日目で、あなたはすでに

fetch と async/await で API からデータを取る
try-catch と response.ok、data.error でエラーを分岐する
ローディング表示とボタン制御を入れる
forecast API で複数日の天気を表示する

という「API 通信アプリの骨格」を手に入れました。

4日目は、この骨格を崩さずに、

天気アイコンの表示
摂氏 / 華氏の切り替え
エラー時の UI の見やすさ
ローディング表示の質を上げる

といった“プロダクト寄りの仕上げ”をしていきます。


今日の完成イメージを先に描く

どんなアプリにしたいか

昨日までのアプリは、
「文字だけで 3 日分の天気を表示する」ものでした。

今日のゴールは、こうです。

都市名を入力して検索
3 日分の天気が「アイコン付き」で表示される
気温を「℃ / ℉」で切り替えられる
ローディング中はスピナー風の表示
エラー時は赤字でわかりやすく表示

つまり、
ロジックはほぼそのままに、
見た目と体験を一段階上げるのが今日のテーマです。


fetch と async/await の「骨格」は変えない

まずは昨日までの基本形を再確認する

API 通信部分は、すでに良い形になっています。

非同期関数であることを示す async
fetch の完了を待つ await
try-catch-finally で成功・失敗・後片付けを分ける
response.ok と data.error でエラーを分岐する

この「型」は、今日もそのまま使います。

例として、3 日分の天気を取る関数はこうでした。

async function fetchForecast(city) {
  const error = validateCity(city);
  if (error) {
    showError(error);
    clearResult();
    return;
  }

  startLoading();

  try {
    const url = buildUrl(city, 3);
    const response = await fetch(url);

    if (!response.ok) {
      showError(`サーバーエラーが発生しました。(${response.status})`);
      return;
    }

    const data = await response.json();

    if (data.error) {
      showError(`エラー:${data.error.message}`);
      return;
    }

    showStatus("3日分の天気を取得しました。");
    renderForecast(data);

  } catch (error) {
    showError("通信に失敗しました。ネットワークを確認してください。");
    console.error(error);

  } finally {
    endLoading();
  }
}
JavaScript

ここで大事なのは、
「fetch の書き方はもう変えなくていい」
という感覚を持つことです。

今日やるのは、
この周りにある UI 部分を育てていくことです。


天気アイコンを表示して「情報を一瞬で伝える」

WeatherAPI のアイコン URL をどう使うか

forecast のレスポンスには、
各日の condition.icon というプロパティがあります。

例として、1 日分の JSON はこうです。

{
  "date": "2026-01-27",
  "day": {
    "maxtemp_c": 28.0,
    "mintemp_c": 20.0,
    "condition": {
      "text": "晴れ",
      "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png"
    }
  }
}

icon は「スキーマ省略 URL」になっているので、
実際には “https:” を付けて使います。

renderForecast にアイコンを組み込む

昨日の renderForecast を、
アイコン付きに書き換えます。

function renderForecast(data) {
  const name = data.location.name;
  const country = data.location.country;
  const days = data.forecast.forecastday;

  let html = "";
  html += `<p>${name} (${country}) の 3 日間の天気</p>`;

  days.forEach((day) => {
    const date = day.date;
    const max = day.day.maxtemp_c;
    const min = day.day.mintemp_c;
    const text = day.day.condition.text;
    const iconUrl = "https:" + day.day.condition.icon;

    html += `
      <div class="day">
        <p>日付:${date}</p>
        <img src="${iconUrl}" alt="${text}" />
        <p>最高:${max} ℃ / 最低:${min} ℃</p>
        <p>天気:${text}</p>
      </div>
    `;
  });

  resultDiv.innerHTML = html;
}
JavaScript

これだけで、
「文字だけの天気」から
「一目でわかる天気」に変わります。

深掘りポイント

ここでやっていることは、
実はとてもシンプルです。

API のレスポンスから
必要な値を取り出す
HTML に埋め込む

fetch や async/await の部分は一切変えていません。
変わっているのは「JSON のどのプロパティを使うか」だけです。


摂氏 / 華氏の切り替えを実装してみる

どこで「単位」を決めるか

WeatherAPI は、
摂氏(temp_c)と華氏(temp_f)の両方を返してくれます。

設計としては、
「どの単位で表示するか」を
アプリ側の状態として持つのが自然です。

例えば、こういう変数を用意します。

let unit = "c"; // "c" or "f"
JavaScript

ボタンやセレクトボックスで切り替えられるようにします。

<button id="unitC">℃</button>
<button id="unitF">℉</button>

JavaScript 側でこう書きます。

const unitCButton = document.getElementById("unitC");
const unitFButton = document.getElementById("unitF");

unitCButton.addEventListener("click", () => {
  unit = "c";
  rerenderIfDataExists();
});

unitFButton.addEventListener("click", () => {
  unit = "f";
  rerenderIfDataExists();
});
JavaScript

ここでのポイントは、
「単位を変えたときに API を再取得しない」
ことです。

すでに取得済みの data を
別の見せ方で表示し直すだけで十分です。

renderForecast を「単位対応」にする

let lastData = null;

function renderForecast(data) {
  lastData = data;

  const name = data.location.name;
  const country = data.location.country;
  const days = data.forecast.forecastday;

  let html = "";
  html += `<p>${name} (${country}) の 3 日間の天気</p>`;

  days.forEach((day) => {
    const date = day.date;
    const max = unit === "c" ? day.day.maxtemp_c : day.day.maxtemp_f;
    const min = unit === "c" ? day.day.mintemp_c : day.day.mintemp_f;
    const text = day.day.condition.text;
    const iconUrl = "https:" + day.day.condition.icon;
    const unitLabel = unit === "c" ? "℃" : "℉";

    html += `
      <div class="day">
        <p>日付:${date}</p>
        <img src="${iconUrl}" alt="${text}" />
        <p>最高:${max} ${unitLabel} / 最低:${min} ${unitLabel}</p>
        <p>天気:${text}</p>
      </div>
    `;
  });

  resultDiv.innerHTML = html;
}

function rerenderIfDataExists() {
  if (lastData) {
    renderForecast(lastData);
  }
}
JavaScript

深掘りポイント

ここで重要なのは、
「API 通信の結果(data)を一度保存しておく」
という発想です。

lastData に保存しておけば、

単位切り替え
表示形式の変更
フィルタリング

などを、
API を叩き直さずに実現できます。

これは「API 通信」と「表示ロジック」を分離する
中級者らしい設計です。


エラーハンドリングを「UI として」整える

エラーをただの文字列で終わらせない

今までは、
statusDiv.textContent に
エラーメッセージを入れていました。

これを少しだけ UI 寄りにします。

例えば、
エラーのときは赤字、
成功のときは緑、
ローディング中はグレー、
というように色を変えます。

.status {
  margin-top: 8px;
}

.status.loading {
  color: #555;
}

.status.success {
  color: #0a0;
}

.status.error {
  color: #c00;
}

JavaScript 側でクラスを切り替えます。

function setStatus(message, type) {
  statusDiv.textContent = message;
  statusDiv.className = "status " + type; // type: "loading" | "success" | "error"
}

function showError(message) {
  setStatus(message, "error");
}

function showStatus(message) {
  setStatus(message, "success");
}

function showLoading() {
  setStatus("天気情報を取得中です…", "loading");
}
JavaScript

fetchForecast の中では、
こう書き換えられます。

async function fetchForecast(city) {
  const error = validateCity(city);
  if (error) {
    showError(error);
    clearResult();
    return;
  }

  showLoading();
  startLoading(); // ボタン無効化など

  try {
    const url = buildUrl(city, 3);
    const response = await fetch(url);

    if (!response.ok) {
      showError(`サーバーエラーが発生しました。(${response.status})`);
      return;
    }

    const data = await response.json();

    if (data.error) {
      showError(`エラー:${data.error.message}`);
      return;
    }

    showStatus("3日分の天気を取得しました。");
    renderForecast(data);

  } catch (error) {
    showError("通信に失敗しました。ネットワークを確認してください。");
    console.error(error);

  } finally {
    endLoading();
  }
}
JavaScript

深掘りポイント

ここでやっているのは、
「エラーを“状態”として扱う」
ということです。

成功
失敗
ローディング

を、
文字列だけでなく
CSS クラスとしても表現することで、
ユーザーにとって直感的な UI になります。


ローディング表示を「ただの文字」から一歩進める

シンプルなスピナー風表示

CSS で簡単なスピナーを作ることもできますが、
まずは「ローディング中だけ表示される要素」を
用意するところから始めます。

<div id="loading" class="loading" style="display: none;">読み込み中...</div>

JavaScript で制御します。

const loadingDiv = document.getElementById("loading");

function startLoading() {
  loadingDiv.style.display = "block";
  searchButton.disabled = true;
}

function endLoading() {
  loadingDiv.style.display = "none";
  searchButton.disabled = false;
}
JavaScript

これで、
「ステータスの文字」とは別に
「ローディング専用の表示」を持てます。

深掘りポイント

ローディング表示は、

ユーザーに「今止まっているのではなく、処理中だ」と伝える
多重クリックを防ぐ
画面の状態をわかりやすくする

という役割を持っています。

非同期処理を書くときは、
「処理の前後にローディングを挟む」
という癖をつけておくと、
どんなアプリでも安定した体験になります。


4日目の全体像をまとめる

ここまでで、
4日目にやったことを整理するとこうなります。

fetch と async/await の「骨格」はそのまま
レスポンスの中から icon や temp_f を追加で使う
単位(℃ / ℉)を状態として持ち、表示だけ切り替える
API の結果(lastData)を保存して、再描画に使う
エラー・成功・ローディングを CSS クラスで表現する
ローディング専用の要素を用意して、処理の前後で表示を切り替える

どれも「新しい難しい文法」ではなく、
「すでに知っているものの組み合わせ方」です。


今日いちばん深く理解してほしいこと

4日目の本質は、

API 通信のロジックはもう十分にできている
これからは「どう見せるか」「どう感じさせるか」を設計していく

という感覚です。

fetch
async/await
エラーハンドリング
ローディング表示

これらはすでにあなたの武器になっています。

そこに、

アイコン表示
単位切り替え
状態に応じた UI
結果の再利用(lastData)

といった“設計の工夫”を足していくことで、
「ただ動くアプリ」から「使いたくなるアプリ」に変わっていきます。

次の 5 日目では、
この天気アプリに「検索履歴」や「お気に入り都市」などを加えて、
さらに“アプリらしさ”を強めていきましょう。

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