2021.02.12

GridsomeでInfiniteScroll

基本的に How to integrate Infinite Loading with Gridsome この記事を見ればお k なんだけど、vue の mixin をつくって汎用的にする方法を書く。 例えばトップページと Tag ページ両方 infinitescroll 実装したいけど同じようなコードを書くのは冗長なので共通化したい時。

./src/mixins フォルダを作ってその中に InfiniteScrollMixin.js などを作成

./src/mixins/InfiniteScrollMixin.js
export default {
  data() {
    return {
      loadedPosts: [],
      currentPage: 1,
    };
  },
  computed: {
    fetchEndpoint() {
      return ``;
    },
  },
  watch: {
    $route(to, from) {
      this.refleshPosts();
    },
  },
  created() {
    this.infiniteInit();
  },
  methods: {
    infiniteInit() {
      this.loadedPosts.push(...this.$page.posts.edges);
    },
    refleshPosts() {
      this.currentPage = 1;
      this.loadedPosts = [];
      if (this.$refs.InfiniteLoading) {
        this.$refs.InfiniteLoading.stateChanger.reset();
      }
      if (this.$page.posts) {
        this.loadedPosts.push(...this.$page.posts.edges);
      }
    },
    wait() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve();
        }, 500);
      });
    },
    async fetch() {
      const { data } = await this.$fetch(
        `${this.fetchEndpoint}/${this.currentPage + 1}`
      );
      return data;
    },
    async infiniteHandler($state) {
      if (this.currentPage + 1 > this.$page.posts.pageInfo.totalPages) {
        $state.complete();
      } else {
        await this.wait();
        const data = await this.fetch();

        if (data.posts.edges.length) {
          this.currentPage = data.posts.pageInfo.currentPage;
          this.loadedPosts.push(...data.posts.edges);
          $state.loaded();
        } else {
          $state.complete();
        }
      }
    },
  },
};

Download

参考記事と基本は同じだけど、汎用的にするためにfetchEndpoint method があるのと、 ページ遷移させた時に refresh させたい用途でrefreshPosts method を route を watch して実行している。 wait method は基本待機時間設定を雑にしてるだけなのでいらなかったら削除して構わない。promise.all しといた方がほんとはよいがな。

で、infinitescroll を実行したいページで以下のように

./src/template/Tag.js
<template lang="pug">
  Layout
    .articles
      transitionGroup(name="fade")
        ItemLimt(:items="loadedPosts", key="tag")
      ClientOnly
        infinite-loading(@infinite="infiniteHandler",spinner="spiral", ref="InfiniteLoading")
          div(slot="no-more") You've scrolled through all the posts ;)
          div(slot="no-results") Sorry, no posts yet :(

</template>
<page-query>
query($id: ID!, $page: Int) {
  allTag(filter: { id : { eq : $id }}) {
    edges {
      node {
        id
      }
    }
  }

  posts: allArticle(filter: { tags: { contains: [$id] }}, perPage: 3, page: $page) @paginate {
    pageInfo{
      totalPages
      currentPage
    }
    edges {
      node {
        id
        title
        date
        path
        catch
        meta {
          description
        }
        tags {
          id
          path
        }
        content
      }
    }
  }
}
</page-query>

<script>
import itemLimt from "@components/itemList.vue";
import InfiniteScrollMixin from "@mixins/InfiniteScrollMixin.js";

export default {
  components: {
    ItemLimt: itemLimt,
  },
  mixins: [InfiniteScrollMixin],
  metaInfo() {
    return {
      title: `${this.tagID}`,
    };
  },
  computed: {
    tagID() {
      return this.$page.allTag.edges[0].node.id;
    },
    fetchEndpoint() {
      return `/tags/${this.tagID}`;
    },
  },
  async mounted() {},
  methods: {},
  beforeDestroy() {},
};
</script>

<style lang="stylus" scoped></style>

mixin を継承したファイル上で fetchEndpoint で endpoint を返してあげれば終わり。 infinitescroll を実装したいファイルで同じように継承して使ってあげれば良い。便利やね。