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

APP JavaScript
スポンサーリンク

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

4日目のテーマは
「ExchangeRate.host 通貨変換アプリを“毎日使えるツール”に育てる」
ことです。

ここまでであなたは、

  • 通貨レート取得
  • 金額変換
  • 入力チェック
  • ローディング表示
  • エラーハンドリング
  • 逆変換ボタン
  • プリセット通貨ペア

といった、通貨変換アプリの基礎をしっかり作ってきました。

4日目では、これらをさらに一段レベルアップさせて、

  • 状態管理(state)
  • お気に入り通貨ペア
  • localStorage 保存
  • UI の整理
  • fetch / async‑await / エラーハンドリングの“型”の強化

を行い、アプリとしての完成度を一気に高めます。


状態管理(state)をアプリの中心に置く

なぜ state が必要なのか

昨日までのコードでは、

  • 現在の金額
  • 選択中の通貨ペア
  • お気に入り通貨ペア
  • ローディング状態

などがバラバラの変数で存在していました。

これをひとつの state にまとめると、

  • どこに何があるか迷わない
  • UI 更新がしやすい
  • localStorage と連動しやすい
  • デバッグがしやすい

というメリットがあります。

state の基本形

const state = {
  isLoading: false,
  favorites: [],
  lastResult: null
};
JavaScript

状態を更新する関数

function updateState(updates) {
  Object.assign(state, updates);
}
JavaScript

これにより、
updateState({ isLoading: true })
のように、どこからでも状態を更新できます。


お気に入り通貨ペア機能を設計する

何を「お気に入り」として保存するか

通貨ペアは、次の 2 つの情報で構成されます。

  • from
  • to

これをオブジェクトとして保存します。

function addFavorite(from, to) {
  const exists = state.favorites.some(
    (fav) => fav.from === from && fav.to === to
  );

  if (exists) return;

  const newFavorites = [{ from, to }, ...state.favorites];
  updateState({ favorites: newFavorites });
  saveFavorites();
  renderFavorites();
}
JavaScript

深掘りポイント

お気に入りは「重複を許さない」ことが大事です。
同じペアを何度も保存しても意味がありません。


localStorage にお気に入りを保存する

保存は JSON に変換して行う

function saveFavorites() {
  try {
    const json = JSON.stringify(state.favorites);
    localStorage.setItem("er_favorites", json);
  } catch (error) {
    console.error("お気に入りの保存に失敗しました", error);
  }
}
JavaScript

起動時に読み込む

function loadFavorites() {
  const saved = localStorage.getItem("er_favorites");
  if (!saved) {
    updateState({ favorites: [] });
    return;
  }

  try {
    const parsed = JSON.parse(saved);
    if (Array.isArray(parsed)) {
      updateState({ favorites: parsed });
    } else {
      updateState({ favorites: [] });
    }
  } catch (error) {
    console.error("お気に入りの読み込みに失敗しました", error);
    updateState({ favorites: [] });
  }

  renderFavorites();
}
JavaScript

深掘りポイント

localStorage は壊れたデータが入る可能性があります。
そのため、

  • JSON.parse の try/catch
  • 配列かどうかのチェック

がとても重要です。


お気に入り一覧を画面に表示する

シンプルな描画関数

const favoritesDiv = document.getElementById("favorites");

function renderFavorites() {
  if (!state.favorites.length) {
    favoritesDiv.textContent = "お気に入り通貨ペアはまだありません。";
    return;
  }

  let html = "<h3>お気に入り通貨ペア</h3>";

  state.favorites.forEach((item, index) => {
    html += `<p data-index="${index}" class="favorite-item">
      ${item.from}${item.to}
    </p>`;
  });

  favoritesDiv.innerHTML = html;

  const items = favoritesDiv.querySelectorAll(".favorite-item");
  items.forEach((el) => {
    el.addEventListener("click", () => {
      const index = Number(el.dataset.index);
      const fav = state.favorites[index];

      fromSelect.value = fav.from;
      toSelect.value = fav.to;

      convertCurrency();
    });
  });
}
JavaScript

深掘りポイント

お気に入りをクリックすると、

  • 通貨ペアをセット
  • すぐに変換

という「ショートカット」になります。


通貨変換結果に「お気に入り追加ボタン」を付ける

renderConversion を拡張する

function renderConversion(from, to, amount, converted, rate) {
  const rateText =
    typeof rate === "number"
      ? `1 ${from} = ${rate} ${to}`
      : "";

  const html = `
    <h3>通貨変換結果</h3>
    <p>${amount} ${from} = ${converted} ${to}</p>
    <p>${rateText}</p>
    <button id="favAddButton">★ この通貨ペアをお気に入りに追加</button>
  `;

  resultDiv.innerHTML = html;

  const favButton = document.getElementById("favAddButton");
  favButton.addEventListener("click", () => {
    addFavorite(from, to);
  });
}
JavaScript

深掘りポイント

「結果を見てからお気に入りに追加できる」
というのは、ユーザー体験としてとても自然です。


ローディング表示を state と連動させる

setLoading を整理する

function setLoading(isLoading, message) {
  updateState({ isLoading });

  if (isLoading) {
    statusDiv.textContent = message || "処理中です…";
  }

  convertButton.disabled = isLoading;
  swapButton.disabled = isLoading;

  const presetButtons = document.querySelectorAll(".preset");
  presetButtons.forEach((btn) => (btn.disabled = isLoading));
}
JavaScript

深掘りポイント

ローディング中は、

  • 変換ボタン
  • 逆変換ボタン
  • プリセットボタン

をすべて無効にすることで、
ユーザーが混乱しないようにします。


fetch / async‑await / エラーハンドリングを“型”として固める

4日目版 convertCurrency(完成形)

async function convertCurrency() {
  const rawAmount = amountInput.value.trim();
  const from = fromSelect.value;
  const to = toSelect.value;

  if (from === to) {
    statusDiv.textContent = "異なる通貨を選択してください。";
    resultDiv.textContent = "";
    return;
  }

  const parsed = parseAmount(rawAmount);
  if (!parsed.ok) {
    statusDiv.textContent = parsed.message;
    resultDiv.textContent = "";
    return;
  }

  const amount = parsed.value;

  setLoading(true, `${amount} ${from}${to} に変換中です…`);
  resultDiv.textContent = "";

  try {
    const url =
      `https://api.exchangerate.host/convert` +
      `?from=${encodeURIComponent(from)}` +
      `&to=${encodeURIComponent(to)}` +
      `&amount=${encodeURIComponent(amount)}`;

    const response = await fetch(url);

    if (!response.ok) {
      if (response.status >= 500) {
        statusDiv.textContent = "サーバー側でエラーが発生しています。時間をおいて再試行してください。";
      } else {
        statusDiv.textContent = `サーバーエラーが発生しました。(${response.status})`;
      }
      return;
    }

    const data = await response.json();

    if (!data || data.success === false) {
      statusDiv.textContent = "レートの取得に失敗しました。";
      console.error("API error response:", data);
      return;
    }

    if (typeof data.result !== "number") {
      statusDiv.textContent = "予期しない形式のデータが返されました。";
      console.error("Unexpected data:", data);
      return;
    }

    statusDiv.textContent = "通貨変換に成功しました。";
    renderConversion(from, to, amount, data.result, data.info?.rate);

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

  } finally {
    setLoading(false);
  }
}
JavaScript

4日目のまとめ

今日あなたがやったことを整理すると、こうなります。

  • state を導入してアプリの状態を一元管理
  • お気に入り通貨ペア機能を実装
  • localStorage に保存して永続化
  • お気に入り一覧をクリックで再変換できるようにした
  • 結果画面に「お気に入り追加」ボタンを追加
  • ローディング表示を state と連動させて一貫した UI にした
  • convertCurrency を「読みやすい流れ」に整理した

どれも新しい文法ではなく、
「fetch / async‑await / エラーハンドリングを“設計として扱う”」
という内容です。


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

4日目の本質は、

「API 通信アプリは、状態管理と UI 設計が整ったときに“ツール”になる」

ということです。

通貨変換アプリは、
fetch → await → JSON → 分岐 → state → UI
という“型”の上に成り立っています。

5日目では、このアプリに
複数通貨の一括変換(Promise.all)・比較表示・設計の整理
などを加えて、さらに中級者らしい一歩を踏み込みます。

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