Vue.jsを使って以下のようなボタンクリック方式のシンプルなページャーを作成したのでメモ📝
以下のような感じでボタンクリックの度にサーバー側に/feeds?page=1
のようなリクエストを送信して結果を元に処理を行うことができます。
<template> <div class="collection"> <p v-for="feed in feeds">{{ feed.title }}</p> <div class="button-pager"> <span v-if="status === 'complete'" class="button-pager__loader-info">全て読み込みました</span> <span v-else-if="status === 'no-result'" class="button-pager__loader-info">結果が取得できませんでした</span> <span v-else-if="status === 'loading'" class="button-pager__loader-info">読み込み中です</span> <span v-else-if="status === 'error'" class="button-pager__loader-info">エラーが発生しました</span> <button v-else class="button-pager__button" @click="feedPagerHandler"> <span>次のページ</span> </button> </div> </div> </template> <script lang="ts" setup> import { onMounted, ref } from "vue"; import FeedCardCollection from "@js/components/molecules/feed/FeedCardCollection.vue"; import { getFeeds } from "@js/services/FeedService"; import type { Feed } from "@js/types/types"; import { usePager } from "@js/composables/Pager"; import CommonButtonPager from "@js/components/molecules/CommonButtonPager.vue"; const feeds = ref<Feed[]>([]); const resetFeedList = () => feeds.value.splice(0); const updateFeedList = (target: Feed[]) => feeds.value.push(...target); const { page, pagerHandler, status } = usePager(); const feedPagerHandler = async () => { await pagerHandler( () => getFeeds({ page: page.value }), (data: Feed[]) => updateFeedList(data), ).catch(error) { console.error(error) }; }; onMounted(() => feedPagerHandler()); </script>
ページャー周りの処理は以下のようなusePager
を返すcomposableを用意して、ロード中や結果なし、全件読み込み完了等のステータス制御をしつつ引数で渡された処理を実行して実行の度にページをインクリメントしてます。今回はさらに読み込む的なUIを想定しているためボタンクリックで実行するようにしてますが、IntersectionObserver等を使って要素の可視性を監視して実行するようにすると無限スクロールみたいな実装にも使えると思います。
import { computed, ref } from "vue"; export type PagerStatus = "initial" | "loading" | "loaded" | "complete" | "error" | "no-result"; export const usePager = () => { const page = ref<number>(1); const status = ref<PagerStatus>("initial"); const isInitial = computed(() => status.value === "initial"); const isLoading = computed(() => status.value === "loading"); const isLoaded = computed(() => status.value === "loaded"); const isNoResult = computed(() => status.value === "no-result"); const isComplete = computed(() => status.value === "complete"); const isError = computed(() => status.value === "error"); const isNoNeededProcess = computed(() => isLoading.value || isComplete.value || isNoResult.value); const resetStatus = () => { page.value = 1; status.value = "initial"; }; const calcSuccessStatus = (resultLength: number, pageNumver: number): PagerStatus => { if (resultLength < 1 && pageNumver <= 1) return "no-result"; if (resultLength < 1 && pageNumver > 1) return "complete"; return "loaded"; }; const pagerHandler = async <T>(process: () => Promise<T[]>, callback: (data: T[]) => void) => { if (isNoNeededProcess.value) return; try { status.value = "loading"; const result = await process(); callback(result); status.value = calcSuccessStatus(result.length, page.value); if (isLoaded.value) page.value += 1; } catch (error) { status.value = "error"; throw error; } }; return { isInitial, isLoading, isLoaded, isComplete, isError, isNoResult, status, page, pagerHandler, resetStatus, }; };
おしまい。