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);
}
}
JavaScript4日目のまとめ
今日あなたがやったことを整理すると、こうなります。
- state を導入してアプリの状態を一元管理
- お気に入り通貨ペア機能を実装
- localStorage に保存して永続化
- お気に入り一覧をクリックで再変換できるようにした
- 結果画面に「お気に入り追加」ボタンを追加
- ローディング表示を state と連動させて一貫した UI にした
- convertCurrency を「読みやすい流れ」に整理した
どれも新しい文法ではなく、
「fetch / async‑await / エラーハンドリングを“設計として扱う”」
という内容です。
今日いちばん深く理解してほしいこと
4日目の本質は、
「API 通信アプリは、状態管理と UI 設計が整ったときに“ツール”になる」
ということです。
通貨変換アプリは、
fetch → await → JSON → 分岐 → state → UI
という“型”の上に成り立っています。
5日目では、このアプリに
複数通貨の一括変換(Promise.all)・比較表示・設計の整理
などを加えて、さらに中級者らしい一歩を踏み込みます。

