vue-infinite-loading使ってますか?無限スクロールが超簡単に実装できるので知らない人は調べてみると幸せになれるかと。
前提
vue-infinite-loadingはデータを読み尽くすと No more data :)
を、そもそも対象データが存在しなければ No results :(
を表示するようになっています。
<template>
<div>
<v-list>
<v-list-item v-for="(item, $index) in data" :key="$index">
</v-list>
<InfiniteLoading @infinite="onEndOfPage"/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import axios, { AxiosResponse, AxiosError } from 'axios';
import InfiniteLoading, { StateChanger } from 'vue-infinite-loading';
@Component({
components: {
InfiniteLoading
}
})
export default class FooList extends Vue {
data = [];
page = 0;
get pageSize(): number {
return 50;
}
async mounted(): Promise<void> {
// サーバーはstringのリストを返し、offsetとlimitでページングが可能とする
const res: AxiosResponse<string[]> = await axios.get<string[]>(`http://localhost:3000/api/foo?offset=0&limit=${this.pageSize}`);
this.data = res.data;
}
private async onEndOfPage($state: StateChanger): Promise<void> {
this.page++;
// サーバーはstringのリストを返し、offsetとlimitでページングが可能とする
const res: AxiosResponse<string[]> = await axios.get<string[]>(`http://localhost:3000/api/foo?offset=${this.pageSize * this.page}&limit=${this.pageSize}`);
this.data = this.data.concat(res.data);
if (res.data.length < this.pageSize) {
$state.complete();
} else {
$state.loaded();
}
}
}
</script>
これを実行すると何が起きるでしょうか。
mounted
はでサーバーから初期データ(最初の50件)を取得しています。
これはDBに50件以上のデータがあれば問題なく50件表示されるでしょう。
次に、下方向にスクロールしていきます。するとInfiniteLoading
がinfinite
イベントを発火させます。
onEndOfPage
では2ページ目のデータをサーバーから取得します。
取得件数に応じて、まだサーバーにデータがある場合は$state.loaded()
を、もうサーバーにデータがない(取得できた件数がページサイズを下回っている)場合は$state.complete()
を呼び出すことで、更に次のページを読み込むか全件読み込みを完了とするかを制御しています。
ここまでは説明している記事は山ほどあるでしょう。
問題
まず、上記のコードで必要なデータは問題なく全件読み込むことはできます。
ただ、vue-infinite-loadingは読み込みが終わった際のメッセージを表示してくれますが、上記のコードだと最後全件読み込まれて$state.complete()
を呼ぶと、リストの末尾にNo results :(
と表示されます(※こうなるには条件があります)。このNo results :(
は本来は表示するデータが1件もなかった場合に表示されるもので、データの読み込みが最後まで終わったときはNo more data :)
になるはずです。
??
原因と回避策
いろいろと試した結果、loaded()
が一度も呼ばれていない状態でcomplete()
が呼ばれると、メッセージがNo results :(
になるようです。なんかvue-infinite-loadingがおかしいのでは?という気もしつつ、対処療法としてloaded()
が必ず呼ばれるようにしてみましょう。
async mounted(): Promise<void> {
// サーバーはstringのリストを返し、offsetとlimitでページングが可能とする
const res: AxiosResponse<string[]> = await axios.get<string[]>(`http://localhost:3000/api/foo?offset=0&limit=${this.pageSize}`);
this.data = res.data;
let loading: InfiniteLoading | null = null;
const isInfiniteLoading = (x: any): x is InfiniteLoading => (x !== null && typeof x === 'object' && typeof x.distance === 'number');
if (isInfiniteLoading(this.$refs.infiniteLoading)) {
loading = this.$refs.infiniteLoading;
}
if (!loading) {
throw new Error('予期しないエラーが発生しました');
}
if (flowerStands.data.length < this.pageSize) {
loading.stateChanger.loaded();
} else {
loading.stateChanger.complete();
}
}
最初にmounted
でデータを読み込む際にもInfiniteLoadingを使ってloaded()
かcomplete()
か然るべき方を呼び出して、vue-infinite-loadingの状態管理下に置きます。こうすることで、最後まで読み込んだときに正しいメッセージが表示されるようになりました。
InfiniteLoadingのインスタンスを取得するために黒魔術っぽいコードが書かれているのはこの記事で解説しています。