VueJS/VueJS

[VueJS] plugin 활용하여 Infinite scroll 구현하기

썸머워즈 2023. 1. 6. 20:07
반응형

Infinite scroll

일명 무한스크롤이라고도 하는데, 보통 무한스크롤을 구현하는 데 있어서 onScroll, Intersection Observer API를 사용하여 만들고는 한다.

 

하지만 이왕 VueJS를 사용하는김에 사용하기 유용한 플러그인이 있을 거 같아서 플러그인 기반으로 무한스크롤을 만들어 보고자 한다.

vue-infinite-loading VS vue-infinite-scroll

vuejs에서 무한 스크롤 관련 플러그인을 찾아보면 일반적으로 저렇게 두 개의 플러그인이 나온다.

이왕 선택하는 거 더 좋은 거 선택하고 싶어서 이것저것 비교해 보았다.

vue-infinite-loading / vue-infinite-scroll

  1. 사용방법
    둘의 사용방법은 그렇게 어려운 거 없이 매우 간단해 보인다. 여기서 둘의 차이점은 vue-infinite-loading은 컴포넌트를 제공하고, vue-infinite-scroll은 지시어만 제공한다는 점이다.
  2. 다운로드 수
    npm사이트에는 각 플러그인별로 우측에 다운로드 수가 출력되는데 이건 아무리 봐도 vue-infinite-loading이 약 3배가량 높게 측정된다.
  3. 수정날짜
    이 역시 아무래도 최근까지 업데이트된 게 좋아 보이는 게 있다. vue-infinite-loading이 상대적으로 최근까지 업데이트가 진행되었다.
  4. 가이드문서
    이게 가장 큰데, 각 플러그인의 가이드문서가 상당한 차이를 보인다. 이것만 보더라도 vue-infinite-loading을 선택하는데 이견이 없다.
  5. 기타
    타입스크립트 유무, vue3호환 (infinite-loading-vue3-ts) 등이 있다.

vue-infinite-loading 사용

결국 저런 이유들로 인하여 vue-infinite-loading 플러그인을 사용하기로 마음먹었다.

vue-infinite-loading 사용 가이드를 살펴보면 어떤 식으로 화면에 출력되는지, 어떻게 사용하는지 친절하게 알려준다.

그리고 의외로 검색을 해보면 상당히 많은 사람들이 vue-infinite-loading 플러그인을 사용하여 개발을 진행했던 것을 볼 수 있다.

npm install vue-infinite-loading -S
yarn add vue-infinite-loading

공식 문서에는 npm기반으로 작성되어 있지만 yarn으로도 설치가 가능하다.

 

그리고 당연히 Vue에서 사용할 수 있게 선언을 해주고!

// main.js or index.js
import InfiniteLoading from 'vue-infinite-loading';

Vue.use(InfiniteLoading, { /* options */ });

 

아래와 같이 아주 간단하게 사용이 가능하다.

<template>
  <!-- v-for문이 끝나는 지점에 infinite-loading component를 사용 -->
  <infinite-loading @infinite="infiniteHandler"></infinite-loading>
</template>

<script>
import InfiniteLoading from 'vue-infinite-loading';

export default {
  components: {
    InfiniteLoading,
  },
  setup() {
    const infiniteHandler = ($state) => {
    	//handler infinite-loading
    }

    return {infiniteHandler}
  }
};
</script>

기본 틀만 해놨기 때문에 공식문서와 이 정도만 설펴보면 어느 정도 사용은 가능할 것이다.

 

좀 더 자세한 예시는 공식문서에서 제공하고 있는 코드예시를 살펴보면 파악하기 쉬울 것이다.

v-for문이 끝나는 지점에 저 infinite-loading 컴포넌트가 대기를 하고 있다고 스크롤이 최하단으로 내려왔을 때를 감지하여 infiniteHandler를 실행시킨다.

 

그리고 상황에 맞게 목록의 총개수와 현재 개수를 비교하여 비동기로 api를 실행시켜 데이터를 가져와 삽입을 시켜주면 되는 원리이다. 그것을 infiniteHandler에서 제어를 하며, $state.loaded()/$state.complete()를 통해 로딩 이미지를 제거하거나 유지시킬 수 있다.

 

뭐 이것들도 다 공식문서에 잘 적혀있으니 보고 개발하는데 아무 문제없을 것이다.

 

이제 문제가 되는 부분은 만약 타입스크립트를 사용한다거나, vue 버전이 맞지 않는다거나, composition api를 사용한다거나 하면 조금씩 달라질 수 있고 그럴 때는 infinite-loading-vue3-ts와 vue-infinite-loading을 서로 비교해주면서 사용해주면 된다.

나 역시 좀 문제가 될 수 있었지만 infinite-loading-vue3-ts npm 사이트를 들어가 보면 Readme화면에 좀 다른 사용방법들이 있는데, 그걸 참고하여 만드니 별문제 없이 적용되었다.

 

아무래도 vue-infinite-loading 사이트에도 ts가 적혀있는 거 보니 ts도 호환이 되는 거 같다.

 

추가적으로 가장 헤맸던 부분은 no-more message가 default로 출력되는 것을 지우는 것에서 좀 헤맸다.

vue2에서 v-slot으로 변경되었는데 가이드 문서에는 그게 적혀있지 않다.

<template #no-more>
  // 이 안에 텍스트를 넣으면 메시지가 변경된다.
  // 그치만 만약 메시지를 사용하고 싶지 않다면 빈 태그를 넣어주면 된다.
  <span></span>
</template>

그래서 위와 같은 방법으로 사용해보니 메시지가 제거되었는데 이게 환경에 따라 다른지는 알 수 없다.

내가 테스트를 해본 환경은 Nuxt2와 Composition 그리고 Typescript 환경이었다.


적다보니 그냥 대략적으로 안내정도만 하였고 직접 구현한 코드는 올리지 않았는데, 사실 공식문서 예제랑 크게 다를게 없어 올리지 않았다. 공식문서 예제를 꼭 살펴보는게 도움이 될 것이다. (아래가 공식문서 예제이다 화면과 같이 보는게 좋기 때문에 공식문서 예제를 추천하는 것이다.)

<div id="app">
  <header class="hacker-news-header">
    <a target="_blank" href="http://www.ycombinator.com/">
      <img src="https://news.ycombinator.com/y18.gif">
    </a>
    <span>Hacker News</span>
  </header>

  <div
    class="hacker-news-item"
    v-for="(item, $index) in list"
    :key="$index"
    :data-num="$index + 1">
    <a target="_blank" :href="item.url" v-text="item.title"></a>
    <p>
      <span v-text="item.points"></span>
      points by
      <a
        target="_blank"
        :href="`https://news.ycombinator.com/user?id=${item.author}`"
        v-text="item.author"></a>
      |
      <a
        target="_blank" 
        :href="`https://news.ycombinator.com/item?id=${item.objectID}`"
        v-text="`${item.num_comments} comments`"></a>
    </p>
  </div>

  <infinite-loading @infinite="infiniteHandler"></infinite-loading>
</div>
const api = '//hn.algolia.com/api/v1/search_by_date?tags=story';

new Vue({
  el: '#app',
  data() {
    return {
      page: 1,
      list: [],
    };
  },
  methods: {
    infiniteHandler($state) {
      axios.get(api, {
        params: {
          page: this.page,
        },
      }).then(({ data }) => {
        if (data.hits.length) {
          this.page += 1;
          this.list.push(...data.hits);
          $state.loaded();
        } else {
          $state.complete();
        }
      });
    },
  },
});
반응형