[Vue.js] 순수 Vue로만 페이지네이션 구현하기 본문

개발 공부 일지/Vue.js

[Vue.js] 순수 Vue로만 페이지네이션 구현하기

스터디에서 Vue.js로 웹페이지를 만드는 프로젝트를 하고 있는데, 사정상 아직 Vuex를 쓰지 못하는 상태에서, 백엔드 개발도 전혀 이뤄지지 않은 상황에서 화면상으로만 페이지네이션을 구현해야 했다. 

 

 

페이지네이션 완성샷

 

 

페이지에 표시되는 데이터를 넘겨주는 부모 컴포넌트 index.vue가 있고, 페이지를 표시하는 하위 컴포넌트인 pagination.vue가 있다. 

 

index.vue

<template>
  <div>
    <order-list
      v-if="paymentInfo !== []"
      v-for="(item, i) in listData"
      :key="i"
      :orderListData="listData"
      :idxData="i"
    />
    <pagination
      :pageSetting="pageDataSetting(total, limit, block, this.page)"
      @paging="pagingMethod"
    />
    <div class="order-none" v-if="paymentInfo === []">거래내역이 없습니다.</div>
  </div>
</template>

 

<order-list> 컴포넌트는 위 완성샷 사진에서도 살짝 보이는, 거래내역을 보여주는 컴포넌트다. 보다시피 부모 컴포넌트인 index.vue에서 "orderListData"의 이름으로 데이터를 전달하고, v-for로 화면에 뿌려준다. 

 

<pagination> 컴포넌트가 페이지를 표시하기 위한 컴포넌트. 페이지를 클릭하면 이전 페이지, 다음 페이지로 넘어갈 수 있게도 만들어주고 현재 페이지가 몇 페이지인지도 보여주는 등등의 기능을 구현할 예정. 

 

pageSetting이라는 이름으로 pageDataSetting 함수의 결과값을 전달한다. pageDataSetting 함수에는 인자로 total, limit, block, this.page 네 가지의 숫자가 들어가는데 이에 대한 설명은 잠시 뒤에.

 

@paging="pagingMethod"는 pagination 컴포넌트에서 클릭 이벤트가 발생했을 때 이를 감지하는 부분이다. pagination 컴포넌트에서 클릭 이벤트가 발생하면 paging이라는 이름으로 클릭 이벤트가 발생한 사실을 전달하고, 이를 전달받으면 부모 컴포넌트인 index.vue에서는 pagingMethod라는 함수를 실행시키게 된다. 

 

<script>
  import orderList from "../../components/orderList/orderList.vue"
  import pagination from "../../components/orderList/pagination.vue"
  import { paymentInfo } from "../../components/orderList/payment.js"

  export default {
    data() {
      return {
        listData: [],
        paymentInfo,
        total: paymentInfo.length,
        page: 1,
        limit: 5,
        block: 5
      }
    },
    components: {
      orderList,
      pagination
    },
    mounted() {
      this.pagingMethod(this.page)
    },
    methods: {
      pagingMethod(page) {
        this.listData = this.paymentInfo.slice(
          (page - 1) * this.limit,
          page * this.limit
        )
        this.page = page
        this.pageDataSetting(this.total, this.limit, this.block, page)
      },

      pageDataSetting(total, limit, block, page) {
        const totalPage = Math.ceil(total / limit)
        let currentPage = page
        const first =
          currentPage > 1 ? parseInt(currentPage, 10) - parseInt(1, 10) : null
        const end =
          totalPage !== currentPage
            ? parseInt(currentPage, 10) + parseInt(1, 10)
            : null

        let startIndex = (Math.ceil(currentPage / block) - 1) * block + 1
        let endIndex =
          startIndex + block > totalPage ? totalPage : startIndex + block - 1
        let list = []
        for (let index = startIndex; index <= endIndex; index++) {
          list.push(index)
        }
        return { first, end, list, currentPage }
      }
    }
  }
</script>

 

index.vue의 스크립트 부분이다. 먼저 상단에서 orderList와 pagination 컴포넌트를 불러온다. 데이터를 뿌려주기 위해 js 파일도 불러주었다. (payment.js 파일에는 JSON 형태의 배열 데이터가 들어있다. 페이지네이션 구현과는 관계 없음)

 

여기서 봐야 할 것은 data() 안의 total, page, limit, block이다. total은 페이지에 담길 전체 데이터의 개수를 의미한다. page는 현재 어느 페이지에 있는지를 나타내기 위한 값으로 디폴트는 첫 번째 페이지를 의미하는 1로 주었다. limit은 한 페이지 당 나타날 데이터의 개수를 의미하며, block은 페이지 리스트에서 한 번에 보여질 페이지 개수를 의미한다. 나는 <<1 2 3 4 5>> <<6 7 8 9 10>> 이렇게 나타나게 하고 싶어 값을 5로 설정했다.

 

이제 methods를 보면, methods에는 pagingMethod와 pageDataSetting이라는 두 개의 함수가 있다.

 

pagingMethod 함수에서는 페이지를 클릭했을 때 일어나는 일들을 정의해놓았다. 우선 한 페이지 당 데이터를 limit개씩 뿌려주기 위해 만든 함수로 이는 limit과 slice 함수를 이용해 만들었다. page를 인자로 받는데, 여기서 page는 하위 컴포넌트인 pagination.vue에서 "paging"이라는 이벤트를 $emit으로 받을 때 여기서 넘겨 받는 값이다. 예컨대 pagination.vue에서 3페이지를 클릭하면, @paging을 통해 3이라는 숫자가 이 함수의 인자로 들어가는 것이다. 그리고 위의 data()에서 선언한 total, limit, block과 이 함수에서 인자로 받은 page값을 다음 함수인 pageDataSetting의 인자로 넘기도록 했다. 

 

mounted()부분을 보면, 아무것도 클릭하지 않은 처음에는, data()에서 미리 지정해 준 page값인 1이 들어갈 수 있도록 함수를 선언해준 것을 볼 수 있다. 

 

pageDataSetting은 "이전", "다음" 아이콘을 클릭했을 때 페이지가 이동될 수 있도록 함과 동시에 페이지를 block 단위로 보여주도록 하는 함수이다. 우선 전체 데이터(total)를 limit으로 나눠 페이지가 총 몇 개가 나와야 하는지를 구한다(totalPage). 이 때 Math.ceil을 써서 나누어 떨어지지 않더라도 1이 올림될 수 있도록 했다. (총 데이터 개수가 11개라면, 11/5=2이지만 3페이지에서 나머지 한 개도 출력되어야 할 것이므로)

 

그리고 좀 더 명시적으로 보여주기 위해 인자로 받은 page 값을 currentPage라는 이름의 변수로 선언해주었다. 

 

first, end는 '이전', '다음' 아이콘을 작동해주기 위한 부분이다. first라는 변수에, 만약 현재 페이지가 1보다 크다면 숫자를 하나씩 줄여나가도록 했고, end에는 만약 현재 페이지가 마지막 페이지가 아니라면 숫자를 하나씩 늘려나가도록 했다. 이를 통해서 이전 아이콘을 클릭하면 전페이지로 이동하고, 다음 아이콘을 클릭하면 다음 페이지로 이동할 수 있게 된다.

 

startIndex부터는 페이지 리스트를 block단위로, 그러니까 여기서는 5개 단위로 표시해주기 위한 부분이다. 식이 어떻게 나왔느냐 하면...... 이 부분은 하나하나 숫자를 직접 써 보면서 규칙을 찾아냈다.(...)

 

마지막으로 first, end, list, currentPage값을 리턴하여 pagination.vue에 전달할 수 있도록 했다.

 

pagination.vue

<template>
  <div>
    <div v-if="pageSetting.list.length" class="page-list">
      <chevrons-left-icon
        class="left-icon"
        v-if="pageSetting.first !== null"
        color="#dfdfdf"
        @click="pageSetting.first !== null ? sendPage(pageSetting.first) : ''"
      />
      <ul>
        <li
          :class="{ active: page === pageSetting.currentPage }"
          v-for="page in pageSetting.list"
          :key="page"
          @click="sendPage(page)"
        >
          {{ page }}
        </li>
      </ul>
      <chevrons-right-icon
        class="right-icon"
        v-if="pageSetting.end !== null"
        color="#dfdfdf"
        @click="pageSetting.end !== null ? sendPage(pageSetting.end) : ''"
      />
    </div>
  </div>
</template>

<script>
  import { ChevronsLeftIcon, ChevronsRightIcon } from "vue-feather-icons"
  export default {
    props: ["pageSetting"],

    components: {
      ChevronsLeftIcon,
      ChevronsRightIcon
    },
    methods: {
      sendPage(page) {
        this.$emit("paging", page)
      }
    }
  }
</script>

 

pagination.vue는 페이지 리스트를 출력해주는 하위 컴포넌트이다. 우선 index.vue에서 pageDataSetting 함수의 결과값을 pageSetting이라는 이름으로 prop으로 내려주었는데, 이 중 first값과 end값이 null인지 아닌지의 여부에 따라 '이전'아이콘과 '다음'아이콘이 표시될 수 있도록 했다. 

 

페이지 리스트는 ul태그를 이용했다. 보면 알겠지만, 별것 없다. pageDataSetting 함수의 결과값만 받아 출력한다. 이 때 현재 페이지의 경우에는 클래스를 추가해 색상을 변경하여 현재 내가 어느 페이지에 있는지를 알기 쉽도록 해주었다.