5日目のゴールと今日やること
5日目のテーマは
「Datamuse アプリを“毎日使える学習ツール”に近づけるために、状態を保存して、使い勝手を一段上げる」
ことです。
技術的な柱は、いつも通りこの 3 つです。
- fetch
- Promise / async-await
- エラーハンドリング
そこに今日は、次の要素を足します。
- ローカルストレージで「お気に入り」「履歴」を保存する
- アプリ起動時に保存データを読み込む
- fetch・async/await・エラーハンドリングの“最終フォーム”を意識する
- 「失敗したときにどう振る舞うか」を、保存機能込みで考える
「ページを閉じても、昨日の自分の学習が残っている」
そんな状態を目指します。
まず「今のアプリの構造」をざっくり整理する
4日目まででできていること
ここまでの Datamuse アプリは、だいたいこんな構造になっているはずです。
- state に word / mode / isLoading / favorites / history / recent
- requestDatamuse で Datamuse への fetch を共通化
- fetchWordsForMode で検索ロジック(モード別・スコア順・エラー処理)
- addHistory / renderHistory で検索履歴
- addRecent / renderRecent で最近検索した単語
- addFavorite / renderFavorites でお気に入り単語
- fetchSuggestions / renderSuggestions で入力補完
今日はここに「保存」と「復元」を足します。
ローカルストレージの基本をおさらいする
文字列しか保存できない、というルール
localStorage は「キーと文字列」を保存する場所です。
localStorage.setItem("key", "value");
const value = localStorage.getItem("key");
JavaScript配列やオブジェクトを保存したいときは、
JSON に変換してから保存します。
const arr = ["apple", "banana"];
localStorage.setItem("fruits", JSON.stringify(arr));
const loaded = JSON.parse(localStorage.getItem("fruits") || "[]");
JavaScriptここでのポイントは、
- 保存時は
JSON.stringify - 読み込み時は
JSON.parse - データがないときに備えて
"[]"や"{}"をデフォルトにする
という「お約束パターン」です。
お気に入り単語をローカルストレージに保存する
state と localStorage をつなぐ
まず、state に favorites がある前提で進めます。
const state = {
word: "",
mode: "ml",
isLoading: false,
favorites: [],
history: [],
recent: []
};
JavaScriptお気に入りを追加する関数を、
「保存込み」の形にします。
function addFavorite(word) {
if (state.favorites.includes(word)) {
showSuccess("すでにお気に入りに追加されています。");
return;
}
state.favorites.push(word);
saveFavorites();
showSuccess(`「${word}」をお気に入りに追加しました。`);
renderFavorites();
}
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);
if (Array.isArray(parsed)) {
state.favorites = parsed;
} else {
state.favorites = [];
}
} catch (e) {
console.error("お気に入りの読み込みに失敗しました", e);
state.favorites = [];
}
renderFavorites();
}
JavaScriptアプリ起動時に一度だけ呼びます。
loadFavorites();
JavaScript深掘りポイント
ここで大事なのは、
- 「お気に入りを追加する」と「保存する」を分けている
- 読み込み時に try-catch を使って「壊れたデータ」に備えている
というところです。
「外部(localStorage)から来るデータは信用しすぎない」
という感覚は、API のレスポンスを扱うときとも共通しています。
検索履歴も保存する
履歴の保存・読み込み
履歴は state.history に{ key, word, mode } の配列として入っている想定で進めます。
保存
function saveHistory() {
localStorage.setItem("dm_history", JSON.stringify(state.history));
}
JavaScript読み込み
function loadHistory() {
const saved = localStorage.getItem("dm_history");
if (!saved) {
state.history = [];
return;
}
try {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed)) {
state.history = parsed;
} else {
state.history = [];
}
} catch (e) {
console.error("履歴の読み込みに失敗しました", e);
state.history = [];
}
renderHistory();
}
JavaScript追加時に保存を呼ぶ
function addHistory(word, mode) {
const key = `${word}::${mode}`;
state.history = state.history.filter((item) => item.key !== key);
state.history.unshift({ key, word, mode });
if (state.history.length > 10) {
state.history.pop();
}
saveHistory();
renderHistory();
}
JavaScript深掘りポイント
履歴は「ユーザーの行動の記録」です。
これを保存することで、
- 昨日どんな単語を調べたか
- どのモードをよく使うか
が一目でわかるようになります。
技術的には単なる JSON の保存ですが、
「状態を時間の向こう側に持ち越す」 という意味で、
アプリの“重み”が一段上がります。
最近検索した単語も保存する(シンプル版)
recent の保存・読み込み
最近検索した単語は、
単純に文字列の配列として扱っている想定です。
保存
function saveRecent() {
localStorage.setItem("dm_recent", JSON.stringify(state.recent));
}
JavaScript読み込み
function loadRecent() {
const saved = localStorage.getItem("dm_recent");
if (!saved) {
state.recent = [];
return;
}
try {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed)) {
state.recent = parsed;
} else {
state.recent = [];
}
} catch (e) {
console.error("最近検索の読み込みに失敗しました", e);
state.recent = [];
}
renderRecent();
}
JavaScript追加時に保存
function addRecent(word) {
state.recent = state.recent.filter((w) => w !== word);
state.recent.unshift(word);
if (state.recent.length > 5) {
state.recent.pop();
}
saveRecent();
renderRecent();
}
JavaScriptアプリ起動時の「初期化フロー」を整える
init 関数を作る
バラバラに呼んでいた初期化処理を、
ひとつの関数にまとめます。
function init() {
loadFavorites();
loadHistory();
loadRecent();
showSuccess("Datamuse 単語ツールを開始しました。");
}
init();
JavaScriptここでのポイントは、
- 「起動時に何が行われるか」が一目でわかる
- 追加の初期化処理(例えばテーマ設定など)もここに足せる
という「入口の見通しの良さ」です。
fetch・async/await・エラーハンドリングの“最終フォーム”を意識する
Datamuse 用の共通関数を再確認する
5日目の時点で、requestDatamuse はこうなっているのが理想です。
async function requestDatamuse(params) {
const baseUrl = "https://api.datamuse.com/words";
const url = `${baseUrl}?${params.toString()}`;
let response;
try {
response = await fetch(url);
} catch (networkError) {
throw new Error("ネットワークエラーが発生しました。接続を確認してください。");
}
if (!response.ok) {
throw new Error(`HTTPエラー(${response.status})が発生しました。`);
}
let data;
try {
data = await response.json();
} catch (parseError) {
throw new Error("レスポンスの解析に失敗しました。");
}
if (!Array.isArray(data)) {
throw new Error("予期しないレスポンス形式です。");
}
return data;
}
JavaScriptここでの深掘りポイントは、
- fetch 自体の失敗(ネットワーク)
- HTTP ステータスエラー
- JSON パースエラー
- データ形式エラー
を、全部「メッセージ付きの Error」として上に投げていることです。
これにより、
上位の関数(検索・サジェストなど)は、
「何をしていて失敗したか」だけを足してユーザーに伝えればよくなります。
保存機能込みのエラーハンドリングを考える
検索側のエラー表示
検索関数は、requestDatamuse から投げられたエラーを受け取って、
「文脈」を足して表示します。
async function fetchWordsForMode(word, mode) {
if (state.isLoading) return;
startLoading("単語を取得中です…");
resultDiv.textContent = "";
try {
const params = buildSearchParams(word, mode);
const data = await requestDatamuse(params);
if (data.length === 0) {
const label = getModeShortLabel(mode);
showError(`${label}が見つかりませんでした。`);
resultDiv.textContent = "";
return;
}
const sorted = [...data].sort((a, b) => (b.score || 0) - (a.score || 0));
const label = getModeShortLabel(mode);
showSuccess(`${label}の取得に成功しました。`);
renderWords(sorted, mode);
addHistory(word, mode);
addRecent(word);
} catch (error) {
const label = getModeShortLabel(mode);
showError(`${label}の取得中にエラーが発生しました:${error.message}`);
console.error(error);
} finally {
endLoading();
}
}
JavaScriptここでのポイントは、
- 保存処理(履歴・最近)は「成功したときだけ」行う
- エラーが起きても、localStorage のデータは壊さない
- エラーのメッセージは「何をしようとしていたか」を含める
という「安全な振る舞い」です。
5日目の全体像をイメージでまとめる
コード全体は長くなるので、構造だけ整理するとこうです。
- state
- word / mode / isLoading / favorites / history / recent
- 保存・読み込み
- saveFavorites / loadFavorites
- saveHistory / loadHistory
- saveRecent / loadRecent
- 共通 API 関数
- requestDatamuse(fetch・async/await・エラーハンドリングの集約)
- 検索ロジック
- buildSearchParams
- fetchWordsForMode
- UI 更新
- renderWords
- renderFavorites
- renderHistory
- renderRecent
- 状態表示
- setStatus / showLoading / showSuccess / showError
- 初期化
- init(保存データの読み込み+初期メッセージ)
今日いちばん深く理解してほしいこと
5日目の本質は、
「fetch・async/await・エラーハンドリングの“型”の上に、
状態の保存(localStorage)を自然に乗せられるようになる」
ことです。
API から取ってきたデータを、
その場で表示して終わりにするのではなく、
- お気に入りとして残す
- 履歴として残す
- 最近として残す
という「時間をまたぐ設計」ができるようになると、
アプリは一気に“自分の道具”になります。


