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

APP JavaScript
スポンサーリンク

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

4日目のテーマは
「Datamuse API アプリを“毎日使えるツール”に近づけるために、状態管理・お気に入り・ローカルストレージ保存を導入する」
ことです。

ここまでであなたは、

  • fetch で API からデータを取得
  • async/await で読みやすい非同期処理
  • try-catch でエラーを扱う
  • サジェスト(sp=)
  • 検索履歴

を実装してきました。

4日目は、アプリとしての完成度を一気に上げるために、

  • 状態管理(state)
  • お気に入り機能
  • localStorage 保存
  • ローディング表示の強化
  • エラーハンドリングの整理

を行います。

今日の内容は、アプリ開発の“中級者の壁”を越えるための重要ポイントが詰まっています。


状態管理(state)を導入する

なぜ state が必要なのか

昨日までのコードでは、
履歴・サジェスト・検索結果などがバラバラの変数で管理されていました。

これをひとつのオブジェクトにまとめると、

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

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

state の基本形

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

状態を更新する関数

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

これだけで、アプリ全体のコードがスッキリします。


お気に入り機能を追加する

お気に入りの構造を決める

お気に入りには、最低限これだけあれば十分です。

  • 単語
  • スコア
  • モード(類義語 / 連想語 / 韻)
function addFavorite(word, score, mode) {
  state.favorites.unshift({ word, score, mode });

  if (state.favorites.length > 20) {
    state.favorites.pop();
  }

  saveFavorites();
  renderFavorites();
}
JavaScript

localStorage に保存する

保存の基本ルール

localStorage は 文字列しか保存できない ため、
配列やオブジェクトは JSON に変換します。

保存:

localStorage.setItem("dm_favorites", JSON.stringify(state.favorites));
JavaScript

読み込み:

const saved = JSON.parse(localStorage.getItem("dm_favorites") || "[]");
JavaScript

保存関数

function saveFavorites() {
  localStorage.setItem("dm_favorites", JSON.stringify(state.favorites));
}
JavaScript

読み込み関数

function loadFavorites() {
  const saved = localStorage.getItem("dm_favorites");

  if (!saved) {
    state.favorites = [];
    return;
  }

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

  renderFavorites();
}
JavaScript

お気に入りを UI に表示する

表示関数

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

  let html = "<h3>お気に入り</h3>";

  state.favorites.forEach((item, index) => {
    html += `
      <p class="fav-item" data-index="${index}">
        ${item.word}(スコア: ${item.score} / ${getModeLabel(item.mode)}
      </p>
    `;
  });

  favoritesDiv.innerHTML = html;

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

      wordInput.value = fav.word;
      modeSelect.value = fav.mode;

      fetchWords();
    });
  });
}
JavaScript

深掘りポイント

お気に入りをクリックすると、
入力欄に反映して再検索できる
という自然な動きになります。

これは、検索処理が fetchWords() に一本化されているからです。


ローディング表示を強化する

状態と UI を連動させる

function startLoading(message) {
  updateState({ isLoading: true });
  statusDiv.textContent = message || "取得中です…";
  searchButton.disabled = true;
}

function endLoading() {
  updateState({ isLoading: false });
  searchButton.disabled = false;
}
JavaScript

深掘りポイント

ローディング表示は、
「アプリが固まっていない」ことを伝える重要な UI
です。

状態(state)と UI を連動させることで、
アプリ全体の動きが統一されます。


エラーハンドリングを整理する

fetchWords の完成形(4日目版)

async function fetchWords() {
  const word = wordInput.value.trim();
  const mode = modeSelect.value;

  if (!word) {
    statusDiv.textContent = "単語を入力してください。";
    resultDiv.textContent = "";
    return;
  }

  const modeLabel = getModeLabel(mode);

  startLoading(`${modeLabel}を取得中です…`);
  resultDiv.textContent = "";

  try {
    const url = `https://api.datamuse.com/words?${mode}=${encodeURIComponent(word)}`;
    const response = await fetch(url);

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

    const data = await response.json();

    if (!Array.isArray(data)) {
      statusDiv.textContent = "予期しない形式のデータが返されました。";
      return;
    }

    if (data.length === 0) {
      statusDiv.textContent = `${modeLabel}が見つかりませんでした。`;
      return;
    }

    statusDiv.textContent = `${modeLabel}の取得に成功しました。`;

    const sorted = [...data].sort((a, b) => (b.score || 0) - (a.score || 0));
    renderWords(sorted, modeLabel);

    addHistory(word, mode);

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

  } finally {
    endLoading();
  }
}
JavaScript

4日目のまとめ

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

  • state にアプリの状態を集約
  • お気に入り機能を追加
  • localStorage に保存して永続化
  • お気に入りから再検索できるように
  • ローディング表示を状態と連動
  • エラーハンドリングを整理
  • fetchWords を「読みやすい流れ」に改善

どれも新しい文法ではなく、
「fetch / async-await / エラーハンドリングの型をどう設計に落とし込むか」
という話です。


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

4日目の本質は、

「状態管理と保存を整えると、アプリは一気に“毎日使えるツール”になる」
ということです。

検索
サジェスト
履歴
お気に入り
ローディング
エラー表示

これらはすべて、
fetch / async-await / try-catch の型の上に乗っています。

5日目では、
UI の改善、検索体験の向上、状態管理の整理などを行い、
さらに完成度の高い Datamuse アプリに育てていきます。

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