最近にわかに盛り上がっているのがPWA(Progressive Web App)です。PWAは固有の技術を指すキーワードではなく、モバイルWebアプリを作るためのベストプラクティスと言えます。
今回から何回かに分けて、mBaaSを使ったPWAを作っていきます。今回は前回まで作ったTodo管理アプリの通信回数を減らしていきます。
前回まで
前回まではタスク追加、更新、削除を行った際に一覧を取得し直していました。また、タスクのステータスごとにデータを取得していました。これだと通信回数が増える上、オンラインとオフライン時の処理分けが増えてしまいます。
そこで、タスクの取得を一回にし、プログラム側でフィルタリングして表示するようにします。また、データの追加/更新/削除処理についてもオンラインとオフライン時を想定してキューに入れる処理を追加します。
キューを追加する処理を追加
まずデータを追加します。
data() { return { allTasks: [], // すべてのタスクを入れておく変数 tasks: [], // 表示するタスクを入れておく変数 task: { text: null }, status: 'active', queues: { // 追加/更新/削除のキュー add: [], done: [], delete: [] } } },
データ取得処理の分割
元々のデータ取得処理は取得以外にもlocalStorageへの保存や Vue のデータ更新処理を行っていましたので、これを分割します。
async fetch() { const tasks = await (navigator.onLine ? this.onlineFetch() : this.offlineFetch()); const cache = this.getCache(); cache[this.status] = tasks; localStorage.setItem('tasks', JSON.stringify(cache)); Vue.set(this, 'tasks', tasks); },
これを以下のようにします。すべてのタスクを取得し、取得後にフィルタリングします。
async fetch() { this.allTasks = await (navigator.onLine ? this.onlineFetch() : this.offlineFetch()); this.filter(); }, save() { localStorage.setItem('tasks', JSON.stringify(this.allTasks)); }, filter() { const status = this.status; Vue.set(this, 'tasks', this.allTasks.filter(task => task.status == status)); this.save(); },
これに伴ってオンラインデータを取得する処理は検索条件をなくします。
async onlineFetch() { return await Task.fetchAll(); },
同様にオフラインキャッシュの処理も簡単になります。
getCache() { const str = localStorage.getItem('tasks'); let tasks = str ? JSON.parse(str) : {}; tasks = this.jsonToClass(tasks || []); return tasks; },
画面を構築した時に実行される created は、localStorage への保存処理が追加されます。
async created() { if (!ncmb.User.getCurrentUser()) { await ncmb.User.loginAsAnonymous(); } await this.fetch(); this.save(); // 追加 },
フィルタ処理の追加
タスク一覧のステータスが変わった時の changeStatus はデータを取得し直しはせず、フィルタリングだけになります。これにより、オンライン通信が減らせます。
async changeStatus(status) { this.status = status; this.filter(); },
タスク処理のオフライン対応
続いてタスクの追加、更新、削除を行った際のオフライン対応を進めます。
追加処理
追加処理は this.queues.add に処理対象のタスクを追加します。同時に this.allTasks に追加することで、オフライン状態の時でもタスクの追加が確認できます。
ポイントとして、オフライン時には local_ ではじまるobjectIdを付与しています。これはオンラインデータではないというフラグになります。後述する更新処理、削除処理ではobjectIdを対象にデータを探しますので、ユニークになるIDを仮に付与しておきます。
async add() { const text = this.task.text; let task = new Task; task .set('text', text) .set('status', 'active') .set('acl', this.getAcl()); if (navigator.onLine) { task = await task.save(); } else { task.set('objectId', `local_${Math.random().toString(36).slice(-8)}`); this.addQueue('add', task); } this.allTasks.push(task); this.task.text = ''; this.filter(); },
更新処理
更新処理の場合、注意するのは処理対象になるデータを探す処理になるでしょう。処理対象のタスクを allTasks の中から探し、ステータスを更新したデータを適用します。オフラインの場合には this.queues.done の中に追加します。
async done(task, event) { const checked = event.target.checked; task.set('status', checked ? 'done' : 'active'); const objectId = task.objectId; const index = this.allTasks.findIndex(task => task.objectId == objectId); this.allTasks[index] = task; if (navigator.onLine) { await task.update(); } else { this.addQueue('done', task); } this.filter(); },
削除処理
削除処理は更新処理とあまり変わりません。ただし配列から対象データを探して削除します。
async destroy(task) { const objectId = task.objectId; const index = this.allTasks.findIndex(task => task.objectId == objectId); this.allTasks.splice(index, 1); if (navigator.onLine) { await task.delete(); } else { this.addQueue('delete', task); } this.filter(); }
キューは localStorage に残す
キューは変数に残したままだと再読込した時に消えてしまいます。そうならないために、localStorage に保存しておきます。
addQueue(action, task) { this.queues[action].push(task); localStorage.setItem('queues', JSON.stringify(this.queues)); },
まとめ
ここまでの処理で、オフラインでもTodoを追加したり、更新、削除できるようになります。
もちろん現在はオンラインデータは更新していませんので、オンラインに戻って再読込するとデータが復元してしまいます。
次回はオンライン/オフラインの通知イベントを使って、キューにあるデータを処理します。
ここまでのコードはNCMBMania/PWA-NCMB at v4にアップロードしてあります。実装時の参考にしてください。