やりたいこと
Vue.js にて、画面に表示するデータを API で取得する場合、API のレスポンスが返ってくるまでの間、画面には何も表示されない時間ができてしまいます。
API のレスポンスが返ってくるまでの一瞬なので、大きな問題ではありませんが、スムーズな画面表示を装うために、できるだけこの何も表示されない時間を作りたくありませんでした。
そこで、コンポーネントの初回表示時に、API で取得したデータをキャッシュし、
次回以降、同じコンポーネントを再表示する時には、初回表示時にキャッシュしたデータを表示しつつ、裏で新しいデータを取得し、こっそりデータを置き換えるようにすることで、二回目以降の画面表示では、この何も表示されない時間を無くすようにしました。
くわしく
たとえば、以下のような 現在の天気一覧を表示する画面 があるとします。
この画面で表示されている天気の情報は、APIで外部から取得しているとします。
APIの実行には、どうしても多少時間がかかってしまうため、画面遷移直後には、API からのレスポンスが返ってくるまでの間、以下のように一瞬だけデータがない "空白の画面" が表示されてしまいます。
問題の挙動
API の実行に時間がかかってしまうことは仕方がありませんが、スムーズな画面遷移を装うために、できるだけこの"空白の画面"を表示したくありません。
そのため、一回目の画面表示時に取得したデータを破棄せず、二回目以降の画面表示では、一回目で取得したデータを表示させてから、API で最新データを取得して差し替えることで、"空白の画面"を回避します。
この記事では、以下の 2つのステップで説明します。
- Step1: 取得したデータをキャッシュする方法
- Step2: 裏で API を実行してデータを取得する方法
サンプルで利用しているAPIについて
サンプルで利用している API は、Qiita の API v2 を利用しています。1
説明の都合上、API を実行するたびに画面表示が変わる必要があるので、上記 API を実行し、レスポンスが返ってきた時間を表示しています。
(連打などは控えてください...)
Step1: 取得したデータをキャッシュする方法
コンポーネントの状態維持
通常、Vue Router による画面遷移や、なんらかの操作によって、コンポーネントを非表示にした場合、
コンポーネントのインスタンスは破棄され、コンポーネントインスタンス内のデータも消えてしまいます。2
そのため、一度 API を利用して取得したデータも、コンポーネントが非表示になったタイミングで、破棄されてしまいます。
これを防ぐためには、<keep-alive>
を利用します。
<keep-alive>
で囲まれたコンポーネントは、インスタンスが破棄されず、内部の状態も維持したままキャッシュされます。
<keep-alive>
のサンプルコード
<keep-alive>
のサンプルコードを以下に用意しました。
入力フォームを含む子コンポーネントを、<keep-alive>
なしと、<keep-alive>
ありでそれぞれ呼び出しており、両者は上部の「表示切り替え」ボタンで表示/非表示が切り替わります。
<keep-alive>
なしの方では、フォームに文字を入力したのち、表示/非表示を切り替えて、再度フォームを表示すると、非表示にしたタイミングでコンポーネントインスタンスが破棄されているため、入力した値が消えてしまっています。
しかし、<keep-alive>
ありの方では、表示/非表示を切り替えても、入力した値が残っています。
これは、<keep-alive>
ありの方では、フォームを非表示にしたときにコンポーネントのインスタンスが、破棄されずに保持されているので、入力した文字のデータも残っているためです。
See the Pen Vue.js keep-aliveについて by Y-KANOH (@y-kanoh) on CodePen.
このように、<keep-alive>
を利用することよって、一度 API で取得したデータを保持することができます。
<keep-alive>
の注意点
<keep-alive>
を利用することで、破棄されるはずだったコンポーネントインスタンスがメモリ上に残ってしまうため、メモリリークが発生してしまう場合があります。
対策として、<keep-alive>
のプロパティである、include
や、exclude
を指定することで、キャッシュするコンポーネントを正規表現や配列で指定し、不要なキャッシュを回避することができます。
また、max
プロパティを指定することで、キャッシュするコンポーネントの最大数を指定する方法もあります。
詳しくは、keep-alive
のAPI リファレンスを参照してください。
さらに、後述するライフサイクルフック deactivated
を利用すれば、コンポーネントが非表示(非活性)になったタイミングで、不要なデータを削除し、メモリを節約することもできます。3
Step2: 裏で API を実行してデータを取得する方法
<keep-alive>
による弊害
<keep-alive>
を利用することで、API で取得したデータを保持することはできました。
次に、コンポーネントを再表示したときに、裏で API を実行し、データを最新のものと差し替える処理を考えますが、ここで問題があります。
<keep-alive>
を利用することで、コンポーネントは破棄さなくなったため、コンポーネントが作成されるときに実行されるライフサイクルフックである created
や、monted
が利用できません。4
<keep-alive>
による弊害のサンプルコード
比較のために、まずは <keep-alive>
を利用しない場合の例を用意しました。
以下の例は、<keep-alive>
を利用__しないで__、子コンポーネントの created
でAPIを実行し、レスポンスが返ってきた時刻を表示しています。
「表示の切り替え」ボタンを押して子コンポーネントを非表示にするたびに、コンポーネントはキャッシュされずに破棄されるため、再度「表示の切り替え」ボタンで子コンポーネントを表示すると、created
が実行され、API が実行されてデータが再取得されます。
そのため、"空白"は表示されますが、子コンポーネントの表示/非表示を切り替えるたびに、表示される時間が更新されています。
See the Pen Vue.js keep-aliveについて(keep-alive なし) by Y-KANOH (@y-kanoh) on CodePen.
一方、以下の例では、<keep-alive>
で囲まれている子コンポーネントの created
でAPIを実行し、レスポンスが返ってきた時刻を表示しています。
子コンポーネントは非表示になってもキャッシュされ、再度表示されるときに created
は実行されません。
そのため、子コンポーネントの表示/非表示を切り替えても、表示される時間が変わりません。
See the Pen Vue.js keep-aliveについて(keep-alive あり) by Y-KANOH (@y-kanoh) on CodePen.
この場合、<keep-alive>
で囲まれたコンポーネントが活性化するときにフックして処理を実行できるライフサイクルフックである、activated
を利用します。
<keep-alive>
で追加されるライフサイクルフック
<keep-alive>
を利用すると、以下のライフサイクルフックが利用できるようになります。
ライフサイクルフック | 実行タイミング |
---|---|
activated |
<keep-alive> で囲まれたコンポーネントが活性化したとき |
deactivated |
<keep-alive> で囲まれたコンポーネントが非活性化したとき |
このライフサイクルフック、Vue.js のライフサイクルフックのページに載ってなくて、最初気づけませんでした...
activated
は、キャッシュしたコンポーネントが活性化したときに実行されるため、API でのデータ取得をここで実行することにより、コンポーネントが表示されたタイミングで再度 API を実行することができます。
ライフサイクルフック activated
を使ったサンプルコード
以下の例では、<keep-alive>
で囲まれている子コンポーネントの created
ではなく、activated
にて API を実行し、レスポンスが返ってきた時刻を取得しています。
そのため、<keep-alive>
を利用していますが、子コンポーネントの表示/非表示を切り替えるたびに、表示される時間が更新されています。
See the Pen Vue.js keep-aliveについて(keep-alive あり 更新あり) by Y-KANOH (@y-kanoh) on CodePen.
これで、画面を切り替えたときには古いデータを表示し、その後 API で取得した最新データと差し替えることができます。
キャッシュを利用した時と利用していない時の比較
以下は、先に記載したサンプルコードです。
<keep-alive>
を利用していないコードでは、子コンポーネント内に表示されるレスポンスが返ってきた時刻が、非表示から表示に切り替えたタイミングで、一瞬、空白になることが確認できます。
(すみません、通信環境によっては一瞬なので見えないかもしれません...)
一方、<keep-alive>
を利用しているコードでは、子コンポーネントを、非表示から表示に切り替えたタイミングで、一瞬、前に表示した時刻が表示され、その後すぐに新しい時刻が表示されることが確認できます。
<keep-alive>
を利用せず、再表示時には一瞬空白が表示されるコード
See the Pen Vue.js keep-aliveについて(keep-alive なし) by Y-KANOH (@y-kanoh) on CodePen.
<keep-alive>
を利用し、再表示時の最初は古いデータを表示するコード
See the Pen Vue.js keep-aliveについて(keep-alive あり 更新あり) by Y-KANOH (@y-kanoh) on CodePen.
まとめ
ポイントは以下の2点です。
-
<keep-alive>
を利用することで、コンポーネントインスタンスと、内部のデータをキャッシュできる -
<keep-alive>
でキャッシュしたコンポーネントインスタンスが活性になるタイミングで、activated
ライフサイクルフックが実行される
1 を利用して API の実行結果をキャッシュし、
2 を利用して API を再度実行して古いデータと差し替える。
これにより、API のレスポンスが返ってくるまでに生じる ”空白の画面” を避けることができます。
参考
-
要素に
display: none;
を付与するだけの、v-show
ディレクティブによって非表示になった場合は除きます。 ↩ -
https://jp.vuejs.org/v2/cookbook/avoiding-memory-leaks.html#%E4%BB%A3%E6%9B%BF%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3 ↩
-
https://jp.vuejs.org/v2/guide/instance.html#%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB%E3%83%95%E3%83%83%E3%82%AF ↩