TL;DR
今日は、JavaScript の localStorage について学びますね。一言でいうと localStorage とは、ブラウザにデータを永続的に保存するための手段です。
開発環境 | バージョン |
---|---|
Next.js | 13.4.4 |
TypeScript | 5.0.4 |
Emotion | 11.11.0 |
React | 18.2.0 |
データをブラウザに保存させる方法として、次のようなものがあります。今回は、localStorage について学びます。
- localStorage
- indexedDB
localStorage とは?
ブラウザに搭載されている機能で、ユーザーの情報を保持することができます。localStorage には、データを保存したり、取り出したり、削除したりするためのメソッドが備えられています。
基本的な使い方
// データを保存
localStorage.setItem('myKey', 'myValue')
// データを取り出す
let data = localStorage.getItem('myKey')
console.log(data) // "myValue"
// データを削除
localStorage.removeItem('myKey')
これらのメソッドを使って、私たちは簡単にデータをブラウザに保存できます。そして、ブラウザを閉じてもデータが消えないので、永続的に保存できるんです。
機密情報の保存はしないように
localStorage
はウェブブラウザ上のデータ保存手段ですが、セキュリティの観点からは非常に限定的なものです。その理由は、データがブラウザ上に平文(暗号化されていない形式)で保存されるため、悪意あるスクリプトによって容易にアクセスされ、読み取られる可能性があるからです。
特に、ユーザーの個人情報、パスワード、金融情報などの機密性の高い情報は、localStorage
に保存するべきではありません。これらの情報を安全に保管するためには、サーバーサイドでの安全なデータベースや、適切な暗号化技術の利用が必要となります。
また、localStorage
は同一オリジンの全てのウィンドウとタブで共有されるため、一部の情報はユーザー間で漏洩する可能性があります。
要するに、localStorage
は非常に便利な機能ですが、使用する情報と目的によってはその使用が適切でない場合もあります。そのため、開発者はどのような情報をlocalStorage
に保存するか、慎重に考えるべきです。
データの保存期間、消失条件
localStorage にはデータの有効期限が存在せず、ユーザーが手動で削除しない限り、データは永続的に保存されます。ただし、いくつかの特異な状況によってはデータが消失する可能性があります。
-
ユーザーがブラウザのキャッシュや Cookie を削除すると、localStorage のデータも一緒に削除されることがあります。
-
ブラウザによっては、ディスクスペースが不足すると localStorage のデータが削除されることがあります。
-
プライバシーを保護するためのブラウザ拡張機能や設定が、localStorage のデータを削除することがあります。
-
特にモバイルデバイスでは、システムがストレージスペースを解放するために localStorage のデータを削除することがあります。
localStorage はブラウザの内部的な仕組みで、キーと値の形式でデータを保存します。キーと値はどちらも文字列として保存されます。localStorage は基本的にはシンプルなストレージで、より複雑な構造のデータは適切に文字列に変換する必要があります。
具体的には、保存された各データ項目はキーと値のペアとして保持され、これらのペアはブラウザのディスク上に保存されます。ブラウザは、各ウェブサイトまたはウェブアプリケーションごとに独自の localStorage エリアを持っており、他のウェブサイトからはアクセスできません。
したがって、内部的には、各ウェブサイトまたはウェブアプリケーションの localStorage データはそれぞれ独立して保存されています。これは、localStorage のデータが同一のオリジンポリシーに従うためで、ウェブサイトは自身のオリジン(つまり同じドメイン、プロトコル、ポート)からのみ localStorage にアクセスできます。
なお、保存されたデータはブラウザを閉じても消去されず、明示的に削除するか、ブラウザのデータ消去機能を使って削除しない限り、永続的に保持されます。各ブラウザは一定の容量制限を持ち、これを超えると新たなデータの保存が拒否されます。大抵のブラウザでは、この制限は約 5MB とされています。
大量のデータを保存したい場合
大量のデータや永続的なデータを保存する必要がある場合は、IndexedDB
など、他のブラウザストレージのオプションを検討するか、サーバー側でデータを管理することを考えると良いでしょう。
どのような形で保存されているのか?
localStorage 自体はオブジェクト形式でデータを保存することはできません。localStorage は、データを「文字列」形式でのみ保存します。それは、基本的には単純なキーと値のペアの形で、どちらも文字列型です。
しかし、JavaScript のオブジェクトを文字列に変換する方法があるため、それを使ってオブジェクトを保存することができます。具体的には、JSON.stringify メソッドを使ってオブジェクトを JSON 形式の文字列に変換し、それを localStorage に保存します。
そして、データを取り出すときには、JSON.parse メソッドを使って文字列をオブジェクトに戻します。これにより、あたかもオブジェクトを直接保存しているかのように扱うことができます。
例えば次のようになります。
// オブジェクトを保存
const obj = { url: 'https://example.com', title: 'Example' }
localStorage.setItem('key', JSON.stringify(obj))
// オブジェクトを取り出す
const storedObj = JSON.parse(localStorage.getItem('key') || '')
console.log(storedObj.url) // 'https://example.com'
console.log(storedObj.title) // 'Example'
このように、localStorage 自体はオブジェクトを直接保存することはできませんが、JavaScript の機能を利用することで間接的にオブジェクトを保存することができます。
現在表示している URL のパラメータなどを保存することはできるのか?
URL のパラメータも文字列であるため、localStorage に保存することは可能です。Next.js を使っている場合は、router.query
で取得できます。
例えば、Next.js では以下のように現在表示している URL のパラメータを取得し、localStorage に保存することができます。
import { useRouter } from 'next/router'
const Component = () => {
const router = useRouter();
// URLパラメータを取得
const params = router.query;
// パラメータを文字列に変換して保存
localStorage.setItem('params', JSON.stringify(params));
return (
// 省略
)
}
ここでrouter.query
は、現在の URL のパラメータを含むオブジェクトです。オブジェクトを使って動的ルーティングの中身を取得することが可能です。
それをJSON.stringify
を使って文字列に変換し、localStorage に保存しています。
保存したデータを取り出すときは、同じようにJSON.parse
を使ってオブジェクトに戻すことができます。
const params = JSON.parse(localStorage.getItem('params') || '{}')
以上のように、現在表示している URL のパラメータを保存し、後で取り出すことができます。そのため、ページ間のナビゲーションやリロード時にパラメータを維持することなどが可能になります。
データを書き込めない場合
ブラウザのローカルストレージにデータを保存しようとしたとき、容量が不足している場合はエラーが発生します。その場合、古いデータを削除してから新しいデータを書き込むことは可能です。
ただし、それを行うには、削除するデータを適切に選択し、エラーハンドリングを正確に行う必要があります。また、ユーザーに影響を与えないように、削除されるデータについては事前に適切に警告したり、許可を得るなどの対応が求められます。
以下に、その基本的なコードを示します。
const key = 'myData'
const data = {
/* some data */
}
try {
localStorage.setItem(key, JSON.stringify(data))
} catch (error) {
if (error instanceof DOMException && error.code === 22) {
// ストレージが満杯である場合のエラーハンドリング
// ここでは、古いデータを削除することを想定しています
localStorage.removeItem('oldKey') // 例えば、「oldKey」というキーのデータを削除します
// 再度データの書き込みを試みる
try {
localStorage.setItem(key, JSON.stringify(data))
} catch (error) {
console.error('データの書き込みに再度失敗しました')
}
} else {
console.error('データの書き込みに失敗しました', error)
}
}
ただし、これはあくまで基本的な例であり、実際のアプリケーションではさまざまなエラーケースやユーザーエクスペリエンスを考慮する必要があります。また、ローカルストレージの制限に頻繁にぶつかる場合は、IndexedDB などの他のストレージオプションを検討することをおすすめします。
React で localStorage を使う
さて、今度は React で localStorage を使ってみましょう。ここでは、あるボタンをクリックすると入力したテキストが localStorage に保存されるというシンプルなアプリを作ります。
import React, { useState } from 'react'
type InputProps = {
placeholder: string
buttonText: string
}
const TextInput = ({ placeholder, buttonText }: InputProps) => {
const [text, setText] = useState('')
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setText(e.target.value)
}
const handleClick = () => {
localStorage.setItem('text', text)
}
return (
<div>
<input type="text" value={text} onChange={handleChange} placeholder={placeholder} />
<button onClick={handleClick}>{buttonText}</button>
</div>
)
}
export default TextInput
上記のコードでは、<TextInput />
というコンポーネントを定義しています。これは単なるテキスト入力フィールドとボタンから成るコンポーネントです。何かテキストを入力して、ボタンをクリックすると、そのテキストは localStorage に保存されます。
handleChange
関数はテキスト入力フィールドの値が変わるたびに呼び出され、その値をtext
ステートに設定します。handleClick
関数はボタンがクリックされると呼び出され、現在のtext
ステートの値を localStorage に保存します。
localStorage のデータを表示する
さて、次は localStorage に保存されたデータを表示するコンポーネントを作ります。ここでは、ページを読み込んだ時に localStorage からデータを取り出し、それを表示します。
import React, { useEffect, useState } from 'react'
const TextDisplay = () => {
const [text, setText] = useState('')
useEffect(() => {
const storedText = localStorage.getItem('text')
setText(storedText ? storedText : '')
}, [])
return <div>{text}</div>
}
export default TextDisplay
このコンポーネントでは、React のuseEffect
フックを使っています。useEffect
フックは、コンポーネントが初めて描画された後(つまり、ページが読み込まれた後)に実行されます。その中で、localStorage からデータを取り出し、それをtext
ステートに設定しています。
ブックマーク機能を作る
ブックマークボタンを押すと、ブックマーク一覧でそれが表示される機能を作ってみます。
まずはお気に入りボタンを作成し、それがクリックされた時に特定の情報(この場合はページの URL とタイトル)を localStorage に保存します。
// components/FavoriteButton.tsx
import { MouseEvent } from 'react'
type Props = {
url: string
title: string
}
const FavoriteButton = ({ url, title }: Props) => {
const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
event.preventDefault()
const favorites = JSON.parse(localStorage.getItem('favorites') || '[]')
const newFavorite = { url, title }
localStorage.setItem('favorites', JSON.stringify([...favorites, newFavorite]))
}
return <button onClick={handleClick}>お気に入りに追加</button>
}
export default FavoriteButton
上記のコードは「お気に入りに追加」ボタンのコンポーネントを作成します。このボタンがクリックされたら、handleClick
関数が実行され、localStorage の 'favorites' というキーに保存されているデータを取得します。まだ何も保存されていない場合は、デフォルトの値として空の配列が返されます。
新しいお気に入り(URL とタイトル)を作成し、それを既存のお気に入り配列に追加します。最後に、新たに作成したお気に入り配列を JSON 形式に変換し、localStorage に保存します。
次に、お気に入り一覧ページを作成します。このページでは、localStorage から保存されているお気に入りの情報を取得し、それを表示します。
// pages/favorites.tsx
import { useEffect, useState } from 'react'
type Favorite = {
url: string
title: string
}
const FavoritesPage = () => {
const [favorites, setFavorites] = useState<Favorite[]>([])
useEffect(() => {
const savedFavorites = JSON.parse(localStorage.getItem('favorites') || '[]')
setFavorites(savedFavorites)
}, [])
return (
<div>
<h1>お気に入り一覧</h1>
{favorites.map((favorite, index) => (
<div key={index}>
<a href={favorite.url}>{favorite.title}</a>
</div>
))}
</div>
)
}
export default FavoritesPage
上記のコードは、お気に入り一覧ページのコンポーネントを作成します。ページが読み込まれたときに useEffect
フックが実行され、localStorage から 'favorites' というキーで保存されているデータを取得します。その後、取得したデータを setFavorites
を使用して favorites の状態に設定します。これにより、ページにお気に入りのリストが表示されます。
以上が JavaScript の localStorage を用いて「お気に入りボタン」の機能を実装する基本的な手順です。ユーザーがボタンを押すと、そのページの URL とタイトルが localStorage に保存され、お気に入り一覧ページでそれが表示されるようになります。この機能はシンプルながらも、ユーザーの利便性を大幅に向上させることができます。