はじめに
WebアプリのフロントエンドをSPAで開発していると、デスクトップのネイティブアプリの様にファイルの読み書きができるといいなぁって思うことがちょくちょくあります。
HTML5
から、WebStorage
というAPIが採用され、フロントエンドでもネイティブアプリのファイルIOの様な感覚でデータの永続化が可能になりました。
WebStorage
にはsessionStorage
とLocalStorage
の2種類がありますが、永続化という点ではLocalStorage
になります。(sessionStorage
はブラウザを閉じるとデータが消えてしまう為)
今回はこのLocalStorage
の使い方を紹介してみたいと思います。
出来上がりはこんな感じです!
Vue
などモダンなフレームの記事によくあるToDoアプリを題材にLocalStorageq
を使ってみたいと思います。
せっかくなので、登録したToDoを永続化させて、使えるアプリにしてみたいと思います。
いつものとおり、出来上がりイメージは次の様な感じです。
見た目は何も変わり映えしませんが、ブラウザを閉じても、PCを再起動しても登録したToDoがクリアされずに表示されます。
今回のコードはここです。 GitHub - Shikataramuno/vue-ts-localStorageDB at Blogアップ
以前に紹介したスクロールリストのコードをベースにTodoをLocalStorage
で永続化する様に改造する方針でやってみましたので、以前の記事も参考にみていただければと思います。
techblo.shikataramuno.com
LocalStorage どう使うの?
LocalStorage
はURLのオリジンごとにデータを永続化でき、しかも仕様上の容量は最低 5MB と画像の永続化などでなければ、実用上十分な容量が確保されています。
実装上はWindow
オブジェクトのlocalStorage
プロパティでアクセスでき、以下APIが定義されています。
API | 操作 |
---|---|
getItem(key:string) | keyの値を取得する |
setItem(key:string, value:string) | keyでvalueを書き込み |
removeItem(key:string) | keyの値を削除する。 |
clear | このドメインのlocalStorage全体を削除する。 |
注意する点としては、APIで渡すデータはいずれもDOMString
型であることくらいです。
JSONオブジェクトそのままでは記録できません(serializeすれば別ですが)ので、JSON.stringify
とJSON.parse
でオブジェクトとstringを型変換してやる必要があります。
LocalStorage を見てみる
LocalStorage
はデベロッパーツールで、その中身を確認できます。
Application -> Local Storage -> ドメイン と辿れば、現在のLocalStorage
の中身が表示されます。
コンポーネント&クラス構成
では、ToDoアプリに行きましょう。
今回、LocalStorage
でデータを永続化させるので、最初にコンポーネント構成と責務分担を簡単に設計してみました。
全体の構成をUMLのクラス図チックに書いてみるとこんな感じです。(厳格なクラス図ではありませんが、そのあたりはご容赦いただき雰囲気を掴んでいただければと思います)
UIコンポーネント
TodoList.vue
です。出来上がりイメージにあるUIを構築します。
created
のLifeCycleメソッドでTodosのインスタンスを取得します。以降はこのインスタンスを経由してLocalStorage
を読み書きします。ToDoクラス
'Todo.ts'です。Todoのモデルとなるクラスで、分類を示すタグ、todoの内容、id番号、完了状況をメンバとして定義しています。ToDosクラス
Todos.ts
です。LocalStorage
を使う上で肝となるクラスで、ここにLocalStorage
へのアクセスをカプセル化します。
Todoのcollection
を保持するのと同時にLocalStorage
へのアクセスを一元化するためにSingleton
パターンを採用してみました。 その他は、CRUDのメソッドをpublic定義しています。SortOrdersクラス
列ごとのソートオーダーを保持するクラスです。
さぁ作ってみましょう
っということでプロジェクトの作成から行きます。
プロジェクトの定義
プロジェクト名を vue-ts-localstorage-db
としてvue create
コマンドでプロジェクトを作成します。
作成時のオプションや可視性(publicやらprivateやら)を省略するための設定、 デバッグ文を出力できるようにするための設定などは以下の記事を参考にしてください。
LocalStorageへのCRUD
今回はLocalStorage
の使い方がメインなのでUIコンポーネントの詳細は割愛します。(すみません)
LocalStorage
へのアクセスはTodos.ts
にカプセル化しています。実装コードは以下のとおりです。
import Todo from './Todo'; const ToDosKeyWord: string = 'Todos'; const IdKeyWord: string = 'id'; export default class Todos { static getInstance(): Todos { console.log('MemberList.getInstance'); if (!this.instance) { console.log('call constructor'); this.instance = new Todos(); } return this.instance; } private static instance: Todos; private todos: Todo[] = []; private id: number = 1; constructor() { console.log('Todos class constructor'); if (IdKeyWord in localStorage) { this.id = JSON.parse(localStorage.getItem(IdKeyWord) as string) as number; } else { localStorage.setItem(IdKeyWord, JSON.stringify(this.id)); } if (ToDosKeyWord in localStorage) { const objs: Todo[] = JSON.parse(localStorage.getItem(ToDosKeyWord) as string) as Todo[]; objs.forEach((obj: Todo) => { const todo: Todo = new Todo(obj.id, obj.tag, obj.todo, obj.complete); this.todos.push(todo); }); } else { localStorage.setItem(ToDosKeyWord, JSON.stringify(this.todos)); } } getTodos(): Todo[] { return this.todos.slice(); } update(todo: Todo): void { const index: number = this.todos.findIndex((td: Todo) => { return td.id === todo.id; }); this.todos[index] = todo; localStorage.setItem(ToDosKeyWord, JSON.stringify(this.todos)); } addTodo(todo: Todo): void { todo.id = this.id; this.todos.push(todo); localStorage.setItem(ToDosKeyWord, JSON.stringify(this.todos)); this.id++; localStorage.setItem(IdKeyWord, JSON.stringify(this.id)); } delete(target: Todo): void { this.todos = this.todos.filter((todo: Todo) => { return todo.id !== target.id; }); localStorage.setItem(ToDosKeyWord, JSON.stringify(this.todos)); } }
まずは、7~14行目のgetInstance()
メソッドでsingleton
パターンを実装しています。
次に20~36行目のコンストラクタでLocalStorage
を検索し永続化データを読み込んでいます。
23行目、28行目でgetItem()
でLocalStorage
から読み込んだデータからJSON.parse()
でオブジェクトを再構築しています。
これは先に述べたとおり、string
型でしか記録できないためです。
今回は、Todoに付与するユニークなIDとTodo配列の二つのキーで永続化しています。 Todo配列をそのまま永続化するのはちょっと乱暴に思えるかもしれませんが、Todoを1件ずつ書き込むと検索、削除でパフォーマンスが低下すると考えたからです。
全てのTodoを一括して永続化し、検索などはメモリ上の配列を対象に行う方式としました。この辺りはデータの容量やアクセスの頻度など対象アプリによりきちんと設計する必要がある部分ですね
38行目のgetTodos()
ではLocalStorage
から読み込んだTodo配列のコピーを返します。
42行目からの update()
では、メモリ上の配列を更新し、配列ごとsetItem()
で上書きしています。
50行目からの addTodo()
では、メモリ上の配列に1件追加(push
)し、同じく上書きしています。
58行目からの delete()
ではメモリ上の配列をフィルタ(削除対象のID以外のTodo)し、上書きしています。
何れもsetItem()
の第二引数でJSON.stringfy()
を使ってオブジェクトをstringに変換して記録していまが、これもstring型でしか記録できないための処理です。
出来上がり!
npm run serve
で動かしてみましょう。
「やらなぁ~あかんこと」の追加
のところにタグとTodoを入力し、「追加」ボタンクリックしてください。Todoがリストに追加されると思います。
いくつか追加してから
ブラウザと閉じて、再度開いてみてください。
きちんとデータが記録されていると思います。(やった!)
謝辞
せっかく永続化するのだから、少しでも使えそうなサンプルと思いTodoを題材にLocalStorage
を試してみました。
ネイティブアプリのファイルアクセスみたいに気軽にアクセスできる点はGood!な感じでした。
シンプルなわりに長い文書になってしまいましたが、最後まで読んで下さりありがとうございました。
何かのお役に立てれば幸いです。