vue-infinite-loadingでデータを最後まで読み込んだ時にNo results :(が出たら確認すべきこと

[vue-infinite-loading](https://github.com/PeachScript/vue-infinite-loading)使ってますか？無限スクロールが超簡単に実装できるので知らない人は調べてみると幸せになれるかと。

## 前提

vue-infinite-loadingはデータを読み尽くすと `No more data :) ` を、そもそも対象データが存在しなければ `No results :(`を表示するようになっています。

```vue
<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()`が必ず呼ばれるようにしてみましょう。

```ts
  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のインスタンスを取得するために黒魔術っぽいコードが書かれているのは[この記事](https://qiita.com/tomoyuki-sato/items/44a9fd681dbaba5628c6)で解説しています。