All Articles

스팀잇(Steemit)기반 앱 만들기 #3 - 무한 스크롤 구현하기

이번 시간에는 스크롤을 내렸을때 글 목록을 자동으로 가져오는 기능을 구현해보도록 하겠습니다. 이런 기능을 인피니티 스크롤(Infinite Scroll) 또는 무한 스크롤이라고 합니다.

아래는 무한 스크롤이 구현된 화면입니다.


이전글

무한 스크롤(Infinite Scroll) 모듈 설치하기

무한 스크롤 기능을 직접 구현해도 되지만, 이 글에서는 vue-infinite-scroll 모듈을 사용하여 구현하겠습니다. 아래와 같이 설치합니다.

$ npm install vue-infinite-scroll --save

글 목록 화면에 무한 스크롤 적용하기

무한 스크롤을 적용하기 위해 Main.vue 파일을 수정합니다. 글목록을 보여주는 영역을 <div>태그로 감쌉니다. 그리고 v-infinite-scroll, infinite-scroll-disabled, infinite-scroll-distance 속성을 추가합니다. 각 속성에 대한 내용은 아래쪽에서 다시 설명하겠습니다.

수정된 코드는 다음과 같습니다. 참고로 코드량이 많아 일부 생략하였습니다.

<v-flex xs12 md9 offset-md3>
  
  <div v-infinite-scroll="loadMore" 
       infinite-scroll-disabled="busy" 
       infinite-scroll-distance="10">
    
    <v-layout row wrap>
      <v-flex xs12 md6 xl4 v-for="d in discussions" :key="d.id">
        
        ... 생략 ...
        
      </v-flex>
    </v-layout>
  </div>
</v-flex>

vue-infinite-scroll 모듈에서 사용가능한 옵션은 아래와 같습니다.

Option Description
infinite-scroll-disabled 이 속성의 값이 TURE 이면 스크롤이 비활성화됩니다.
infinite-scroll-distance v-infinite-scroll 함수가 실행되기 전, 요소 하단과 뷰 포트 하단 사이의 최소 거리입니다. 기본값은 0입니다.
infinite-scroll-immediate-check 바인딩 되자마자 즉시 directive 을 확인해야 함을 의미합니다. 내용이 스크롤 가능한 컨테이너를 채울만큼 많지 않을 때 유용합니다. 기본값은 true 입니다.
infinite-scroll-listen-for-event Vue 인스턴스에서 이벤트가 생성되면 스크롤을 다시 검사합니다.
infinite-scroll-throttle-delay 이번 확인 시간과 다음 확인 시간 사이의 간격(밀리 초). 기본값은 200입니다.


이제 data 오브젝트에 무한 스크롤 기능 활성화 여부를 저장할 busy 변수를 추가합니다. 그리고 무한 스크롤 기능 작동시 호출되는 loadMore 함수를 구현합니다. loadMore함수에는 앞서 구현하였던, 최근글을 가져오는 getDiscussions 함수를 호출하도록 하겠습니다. getDiscussions 함수 호출 전에 busy값을 true로 변경하여 무한 스크롤이 중복하여 작동 하지 않도록 합니다.

export default {
  data: () => ({
    discussions: [],
    busy: false
  }),
  
  ... 생략 ...
  
  methods: {
    loadMore: function () {
      this.busy = true // 무한 스크롤 기능 비활성화
      this.getDiscussions()
    },

글을 가져온 후에는 무한 스크롤 기능을 다시 활성화 해주어야 합니다. 그래서 getDiscussions 함수를 다음과 같이 수정합니다. getDiscussionsByCreated함수의 콜백에서 busy값을 다시 false 로 변경합니다. 그리고 새로 가져온 데이터가 기존 데이터를 유지하면서 추가될 수 있도록 discussions 변수에 담아주는 코드를 아래와 같이 수정합니다.

// 스팀잇 최근글 가져오기
steem.api.getDiscussionsByCreated(query, (err, result) => {

  if (!err) {
      
    // 무한 스크롤 활성화
    // 가져온 글갯수가 10개 이상인 경우만 작동
    if (result.length > 10) { 
      this.busy = false 
    }
    
    // 가져온 데이터를 items에 담는다.
    const items = result.map(item => { // # 수정됨
    
      ... 생략 ...
    })

    // items 배열을 기존 discussions 배열과 합친다.
    this.discussions = this.discussions.concat(items)
  }
})

그리고 우리는 loadMore함수에서 스팀잇 글을 가져오도록 수정했기 때문에, created 함수에서는 스팀잇 글을 가져오는 getDiscussions 함수를 더 이상 호출하지 않도록 제거합니다.

created () {
  // this.getDiscussions() // # 제거됨
}


위의 코드를 실행해보면 계속 동일한 글 목록만 가져오는 것을 볼 수 있습니다. 왜냐하면 우리는 같은 페이지의 글목록을 가져오고 있기 때문입니다. 다음 페이지의 글을 가져오기 위해서는 steem.api.getDiscussionsByCreated 함수를 호출할 때 permlinkauthor 값을 넘겨줘야합니다. 다음 글을 가져오기 위해 이 두 값을 사용하려면, 글을 가져올때마다 마지막 값을 기억하고 있어야합니다. 아래와 같이 data 오브젝트에 next 오브젝트를 추가합니다. next 오브젝트는 permlinkauthor 를 가집니다.

data: () => ({
  discussions: [],
  busy: false,
  next: {
    permlink: null,
    author: null
  }
})

그리고 methods 에 있는 getDiscussions 함수를 다음과 같이 수정합니다. 스팀잇 최근글을 가져오는 함수를 호출할때 넘겨주는 query 오브젝트에 start_permlinkstart_author 추가하고, next.permlinknext.author 값을 넘겨줍니다. 마지막으로getDiscussionsByCreated 함수 콜백에서 가져온 글목록에서 마지막 데이터의 permlinkauthor 값을 각각 next.permlinknext.author 에 저장합니다.

getDiscussions () {
  let query = {
    tag: 'kr',
    limit: 10,
    start_permlink: this.next.permlink, 
    start_author: this.next.author
  }
  
  // 스팀잇 최근글 가져오기
  steem.api.getDiscussionsByCreated(query, (err, result) => {

    ... 생략 ...
    
    this.next.permlink = item.permlink // 마지막 permlink 값 저장  
    this.next.author = item.author // 마지막 author 값 저장

    return {
      id: item.id, // # 추가됨
      image: image,
      author: item.author,
      author_reputation: item.author_reputation,
      title: item.title,
      created: item.created,
      body: item.body.substr(0, 200),
      category: item.category,
      permlink: item.permlink,
      url: item.url,
      payout_value: item.payout_value,
      net_votes: item.net_votes,
      children: item.children
    }
  })


지금까지 작업한 코드를 실행해보면, 다음에 가져온 글 목록의 첫번째 글이 이전에 가져온 글 목록의 마지막 글이랑 동일하다는 것을 확인할 수 있습니다. 일부러 이렇게 의도한 것인지는 잘 모르겠습니다. 가져온 글목록의 글ID를 체크하여 글ID가 서로 동일하면 기존 글목록에 추가하지 않도록 합니다. 그래서 추가로 가져온 글 목록에서 첫번째 항목을 제거하도록 하겠습니다.

// 스팀잇 최근글 가져오기
steem.api.getDiscussionsByCreated(query, (err, result) => {
  this.busy = false
  if (!err) {

    // 가져온 글의 첫번쨰 항목 제거
    if (this.discussions.length > 0) {
      result = result.slice(1)
    }
    
    ... 생략 ...


글을 가져올때 아무런 변화가 없다가 화면에 글들이 갑자기 나타나면 아무래도 심심하기 때문에, 글을 가져올 때 화면에 로딩 이미지를 보여주도록 하겠습니다. 다음과 같이 <v-progress-circular> 태그를 글목록을 출력하는 부분 아래에 삽입합니다. 그리고 v-show="busy" 옵션을 사용하여 무한 스크롤 기능이 작동 중일때만 로딩 이미지가 보이도록 합니다.

    ... 생략 ...
  </v-flex><!-- // v-for END -->
</v-layout>
<div class="text-xs-center mt-3" v-show="busy">
  <v-progress-circular
            indeterminate
            color="primary">
  </v-progress-circular>
</div>

오늘 작업은 여기까지입니다.

여기까지 읽어주셔서 감사합니다.


전체 소스 내용은 github에서 볼 수 있습니다.


Published 3 Aug 2018

안피곤의 블로그입니다.