localStorage で保存する
このレッスンで分かること
localStorage.setItem(キー, 値)で残し、getItem(キー)で読み出します- 保存できるのは文字列だけなので、配列やオブジェクトは
JSON.stringifyとJSON.parseを挟みます
リロードすると、選んだ設定が毎回もとに戻る
前回のダークモード切り替えは、押せばちゃんと色が変わります。ところがページを読み込み直すと、いつも明るい見た目から始まってしまいます。変数も classList の状態も、ページが開いているあいだだけメモリの上にあるものだからです。リロードすると JavaScript は 1 行目から動き直し、前の状態はどこにも残っていません。
設定を覚えさせるには、ページの外に置き場所が要ります。いちばん手軽なのが localStorage です。ブラウザがサイトごとに用意する小さな保管庫で、書いた値はタブを閉じてもパソコンを再起動しても残ります。
| 呼び出し | すること | 戻り値 |
|---|---|---|
| setItem(キー, 値) | キーの名前で値を保存する | なし |
| getItem(キー) | キーの値を読み出す | 文字列。無ければ null |
| removeItem(キー) | そのキーを消す | なし |
JavaScript
localStorage.setItem("draft", "書きかけの本文");
console.log(localStorage.getItem("draft")); // 書きかけの本文
localStorage.removeItem("draft");キーは自分で決める名前です。同じキーに setItem すると前の値は上書きされます。中身は開発者ツールから誰でも見られるので、覚えさせてよいのは、テーマの設定や下書きのような、見られても困らないものだけです。
入るのは文字列だけ
localStorage に入れられるのは文字列だけです。数値や真偽値は勝手に文字列へ変換され、配列やオブジェクトに至っては [object Object] という役に立たない文字列になります。
JavaScript
localStorage.setItem("count", 3);
console.log(localStorage.getItem("count") + 1); // "31" になってしまうそこで、まとまったデータは JSON.stringify で文字列に直してから保存し、読むときに JSON.parse でもとの形に戻します。
JavaScript
const settings = { fontSize: 16, compact: true };
localStorage.setItem("settings", JSON.stringify(settings));
const raw = localStorage.getItem("settings");
const restored = JSON.parse(raw);
console.log(restored.fontSize); // 16getItem は、そのキーが無いときに null を返します。null を JSON.parse に渡すとエラーになるので、読んだ直後に「まだ何も保存されていないとき」の分岐を必ず書きます。
保存に失敗すると、その先が全部止まる
localStorage は必ず使えるとは限りません。ブラウザの設定で保存を切っている人もいますし、プライベートウィンドウや、このレッスンのプレビューのように制限された枠の中では、触った瞬間にエラーになることがあります。エラーが出ると、その行から下の JavaScript が全部止まります。設定が覚えられないだけならまだしも、時計もフォームも動かなくなるのでは割に合いません。
なので、保存まわりは try で囲んで、失敗しても先へ進めるようにします。
JavaScript
function saveDraft(text) {
try {
localStorage.setItem("draft", text);
} catch (error) {
console.log("この環境では保存できません");
}
}
function loadDraft() {
try {
return localStorage.getItem("draft");
} catch (error) {
return null;
}
}読めなかったときに null を返しておけば、呼ぶ側は「まだ何も保存されていない」ときと同じ扱いで済みます。保存できてもできなくても、ページの操作は壊れません。
保存は書くだけでは記憶になりません。読み出す、画面へ当てる、変えたらまた保存する。この 3 つがそろって初めて、リロードしても同じ状態で始まります。
よくある間違い
- 読み出した文字列を真偽値として使う —
setItem("dark", false)で入るのは"false"という文字列で、これは truthy です。if (getItem("dark"))は必ず中へ入るので、=== "true"のように中身の文字列で比べます - 読み込み時の反映を書き忘れる — 押すと切り替わるのにリロードで戻ってしまうときは、保存を読んで画面へ当てる 1 行が抜けています
課題
- ヘッダーに、設定を消す id が theme-reset の button と、いまの状態を出す id が theme-status の p を足す
- saveTheme と loadTheme と clearTheme の 3 つの関数を作り、中で setItem と getItem と removeItem を try で囲んで呼ぶ
- applyTheme 関数を作り、渡された値が dark なら body に dark を付け、そうでなければ外す。あわせてボタンの文字と theme-status を書き換える
- 読み込んだ直後に applyTheme に loadTheme の結果を渡して呼び、theme-toggle を押したら切り替えて保存する
- theme-reset を押したら保存を消し、ライトモードに戻す