独自のSuspense機能について

標準のSuspense機能では、下記のようなちょっと不便な点があるようなので他サイトを参考に独自のSuspense機能を作成する

  • コンポーネント化する必要がある
  • 初回のロードのみで再ロードに対応していない

components/MySuspense.vue

<template lang="pug">
div
  template(v-if="status === PromiseStatus.PENDING")
      slot(name="pending")
  template(v-if="status === PromiseStatus.FULFILLED")
      slot(name="fulfilled", :data="resultData")
  template(v-if="status === PromiseStatus.REJECTED")
      slot(name="rejected", :data="resultData")
</template>
<script lang="ts">
import {defineComponent, ref, reactive} from 'vue';

const PromiseStatus = {
  PENDING: 1,
  FULFILLED: 2,
  REJECTED: 9,
} as const;

export default defineComponent({
  name: 'MySuspense',
  props: {
    promise: {type: Promise},
    reloadTime: {type: Number},
  },
  setup(props, context) {

    type PromiseStatusType = typeof PromiseStatus[keyof typeof PromiseStatus]
    const status = ref(PromiseStatus.PENDING as PromiseStatusType);
    const resultData = reactive({});

    return {
      status,
      resultData,
      PromiseStatus,
    };
  },
  watch: {
    reloadTime: {
      immediate: true,
      handler(): void {
        this.status = PromiseStatus.PENDING;
        const {promise: myPromise} = this.$props;
        if (!myPromise) {
          return;
        }
        myPromise.then((resolveData: any) => {
          this.resultData = resolveData;
          this.status = PromiseStatus.FULFILLED;
        }).catch((errorData: any) => {
          this.resultData = errorData;
          this.status = PromiseStatus.REJECTED;
        });
      },
    },
  },
});
</script>
<style lang="scss" scoped>
</style>

views/UserListPageEx.vue

<template lang="pug">
div
  p Reload-Time:{{reloadTime}}
  MySuspense(:promise = "myPromise", :reloadTime="reloadTime")
    template(#pending)
      p pennding...
    template(#fulfilled = "{data}")
      ul
        li(v-for="user in data" :key="user.userId") {{user.userName}}
    template(#rejected = "{data}")
      p Error{{data.message}}
  div
    button(@click="getUsers") 更新
</template>

<script lang="ts">
import {defineComponent, reactive, ref} from 'vue';
import {UserService, IUser} from '@/service/UserService';
import MySuspense from '@/components/MySuspense.vue';
export default defineComponent({
  name: 'UserListPageEx',
  components: {
    MySuspense,
  },
  setup() {
    const myPromise: any = reactive(undefined);
    const reloadTime = ref(0);
    return {
      myPromise,
      reloadTime,
    };
  },
  methods: {
    getUsers() {
      console.log('### UserListPageEx#getUsers() called.');
      const users: Array<IUser> = UserService.getUsers();
      console.log(users);
      this.myPromise = users;
      this.reloadTime = Date.now();
    },
  },
});
</script>

service/UserService.ts

/* eslint-disable require-jsdoc */
import axios from 'axios';

export interface IUser {
    id: number,
    name: string;
}

async function sleep(ms: number) {
  return new Promise((r) => setTimeout(r, ms));
}

export class UserService {
  public static async getUsers(): Promise<IUser[]> {
    console.log('### UserService#getUsers() called.');
    await sleep(1000);
    const response = await axios.get('/api/v1/users/');
    const users = response && response.data && response.data.users ?
        response.data.users : [];
    return users as IUser[];
  }

  public static async getUser(id: string): Promise<IUser> {
    console.log('### UserService#getUser() called.');
    const response = await axios.get('/api/v1/users/' + id);
    const user = response && response.data ? response.data : {};
    return user as IUser;
  }
}