Vue.jsで無限スクロールをライブラリなしの自前で実装してみた(Laravel + Vue.js)

業務でとある予約システムを開発する機会があり、その中で無限スクロールを実装しましたが、
取得するデータが条件付きで単純な取得データの個数を一定にして行うことができなかったのでライブラリなしで実装しました🙋‍♂️

ライブラリを使った記事は割とあるけど自前の方はあんまりないなーと思ったので自分のメモも含めて残します🌟

僕自身、vue.jsがかなり初心者だったため、こう書いたらもっといいよとかあればコメントで教えて下さい🙇‍♂️

本当はもう少し複雑なんですが、必要最低限のみ載せます🙆‍♂️

```Seats.vue

<template>
  <div class="seat-list">
     <div v-for="seat in seats">
         <div class="seat">
            <div>{{ seat.value }}</div>
         </div>
     </div>
     <!--ロード中のアニメーション-->
     <div class="loader-wrap" v-show="loading">
       <div class="text">取得中...</div>
     </div>
  </div>
</template>
<script>
export default {
  props: {
    //予約枠のデータ
    propSeatsData: {
      type: Array,
      default: [],
    },
    eventData: {
      type: Object,
      default: () => {},
    },
    initialStartDay: {
      type: String,
      default: '',
    },
    initialIsLastPage: {
      type: Boolean,
      default: false,
    },
    endpoint: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      //ロード中のアニメーション
      loading: false
      //予約枠のデータ
      seats: this.propSeatsData,
      //イベントデータ
      event: this.eventData,
      //取得する開始日
      startDay: this.initialStartDay,
      //非同期で取得中 通常: false, 通信中: true
      itemLoading: false,
      //まだ取得するデータが存在する: false, もう存在しない: true
      isLastPage: this.initialIsLastPage,
    },
  },
  mounted(){
    window.onscroll = () => {
      //一定位置以上スクロールされればtrueを返す
      let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight >= document.documentElement.offsetHeight;

      //trueでデータ取得
      if(bottomOfWindow) {
        //無限スクロールでデータ取得
        this.getSeats();
      }
    }
  },
  methods: {
    //予約枠データ取得（無限スクロール）
    getSeats () {
      if (this.itemLoading) return //読込中は再読み込み防止
      if (this.isLastPage) return  //取得データがもう存在しない場合は行わない

      //読込中 true
      this.itemLoading = true
      //ロード中のアニメーション表示
      this.loading = true 

      //非同期通信
      axios.post(this.endpoint, {
        //送信するデータ
        date: this.startDay,
        event_id: this.event.id,
      })
      .then((responce) => {
        //取得したデータを追加
        this.seats.push(...responce.data.seats_data)
        //取得するスタート日を更新
        this.startDay = responce.data.start_day
        //ローディングアニメーション非表示
        this.loading = false
        //読込中 false
        this.itemLoading = false
        //まだ取得するデータが存在する: false, もう存在しない: true
        this.isLastPage = responce.data.is_last_page
      })
      .catch((error) => {
          //エラー出力
          console.log(error)
          //ローディングアニメーション非表示
          this.loading = false
          //読込中 false
          this.itemLoading = false
      });
    },
  },
```

下記のコードの部分で、
通信の重複を回避するためにitemLoadingを通常はfalse、通信中はtrueにしてあげます。
データがまだ存在するかどうかはController側で工夫して真偽値を返してあげるといいですね🙋‍♂️

```Seats.vue
data() {
  return {
    //非同期で取得中 通常: false, 通信中: true
    itemLoading: false,
    //まだ取得するデータが存在する: false, もう存在しない: true
    isLastPage: this.initialIsLastPage,
  }
},
```


一番下までのスクロールを検知したい場合は


```Seats.vue
mounted() {
  window.onscroll = () => {
    //一定位置以上スクロールされればtrueを返す
    let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight >= document.documentElement.offsetHeight;
    
    if (bottomOfWindow) {
      下までスクロールした時に実行したい処理を書く
    }
  }
},
```

もし、無限スクロール + selectとかで取得するデータをユーザーが自由に選べる場合は
optionタグ内に :disabled="disabled" を入れてあげて通信中はtrueにすればその間はユーザーはselectを選択できなくなります🙆‍♂️
通信の重複防止です🙆‍♂️

```Seats.vue
<select @change="changeDate">
   <option v-for="day in days" :disabled="disabled">{{ day }}</option>
</select>
```

```Seats.vue
data() {
  return {
    disabled: false,
  }
}
```

メソッド内で⬇

```Seats.vue
methods: {
  getSeats() {
    //selectを選択させない
    this.disabled = true
    
    処理...

    //解除
    this.disabled = false
  }
}

```

とかしてあげれば良い感じに通信の重複は防げるかなと🙌

vue.jsはpropsで渡ってきたデータに、pushしてデータを追加すれば勝手に描画してくれるので
jqueryとかを使うよりもずっと簡単にできるなと実感しました🙆‍♂️

おわり🌟

参考記事
【ライブラリ無し】Laravel + vue で無限スクロール(infinite scroll)を1から実装
[https://inokawablog.org/laravel/laravel-vue-infinite-scroll-scratch/](https://inokawablog.org/laravel/laravel-vue-infinite-scroll-scratch/)