카테고리 없음

Vue.js 페이징 처리

데이터박물관 2021. 9. 12. 12:19

참고한 블로그: https://pewww.tistory.com/5

 

Vue.js로 간단한 페이징 구현

안녕하세요! 오늘은 Vue.js로 간단한 페이징을 구현해보려합니다~ 되게 간단하게 할 수 있지만, 저는 vue-cli를 이용하여 구현하겠습니다.  설정이 기억나지 않으시는 분들은 이 글을 참고해주세

pewww.tistory.com

 

vue-cli를 통해 구현하였습니다.

 

API 데이터는

https://jsonplaceholder.typicode.com/todos 해당 사이트에서 가져왔습니다. 

 

코드 실행 결과)

api를 대충 받아왔는데, title 부분이 영어가 아닌 다른 언어네요 ..

 

<template>
  <div id="app">
    <h1>PAGING ASSIGNMENT</h1>
    <PaginatedList/>
  </div>
</template>

<script>
// import axios from 'axios'
import PaginatedList from './components/PaginatedList.vue'

export default {
  name: 'app',
  components: {
    PaginatedList,
  },

  created () {
    // await axios({
    //   method: 'get',
    //   url: 'https://jsonplaceholder.typicode.com/todos',
    // })
    //   .then(response => {
    //     console.log('API 데이터 결과', response);
    //     const result = response.data.filter((item) => {
    //       return item.id <= 183 // 총 Row수 : 183개
    //     })
    //     this.todoList = result;         // API 데이터 셋
    //     this.totalRow = result.length;  // 총 ROW 수
    //   })
    //   .catch(err => {
    //     console.log(err);
    //   })
  },

  data: function () {
    return {
      // todoList: [],  // API 데이터 (할 일 목록)
    }
  },

}

</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

App.vue 컴포넌트입니다.

특별한 건 없고 PaginatedList를 하위 컴포넌트로 연결했습니다.

 

<template>
  <div>
    <table>
      <tr>
        <th>ID</th>
        <th>이름</th>
        <th>할 일</th>
        <th>완료여부</th>
      </tr>
      <tr v-for="p in slicedTodo" :key="p.id">
        <td>{{ p.id }}</td>
        <td>
          {{ 
             p.userId === 1 ? '홍길동' : 
             p.userId === 2 ? '김길동' : 
             p.userId === 3 ? '나갑동' : 
             p.userId === 4 ? '오동동' : 
             p.userId === 5 ? '강호동' : 
             p.userId === 6 ? '팀쿡' : 
             p.userId === 7 ? '짐캐리' :
             p.userId === 8 ? '조인성' :
             p.userId === 9 ? '스티브잡스' : '기타 아무개'
          }}
        </td>
        <td>{{ p.title }}</td>
        <td>{{ p.completed }}</td>
      </tr>
    </table>
    <div class="btn-cover">
      <span class="page-count">{{ pageNum }} / {{ totalPageCnt }} 페이지</span>
      <br/>
      <br/>
      <button @click="prevPage" :disabled="pageNum === firstPage" class="page-btn">이전</button>
      <li style="display: inline-block" v-for="eachPage in pageArr" :key="eachPage">
        <button :disabled="eachPage === pageNum" class="each-page" :class="{'page-active' : eachPage === pageNum}" @click="changePage(eachPage)">
          {{eachPage}}
        </button>
      </li>
      <button @click="nextPage" :disabled="pageNum === lastPage" class="page-btn">다음</button>

    </div>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  name: 'paginatedList',
  
  data () {
    return {
      todoList: [], // 할 일 목록 (게시글)
      pageNum: 0, // 현재 페이지
      pageArr: [],  // 페이지 배열
      pageRange: 10,  // 출력되어야 할 페이지 범위(10페이지 고정)
      listSize: 10, // 한 페이지 당 출력할 row 개수
      totalPageCnt : 0, // 총 페이지 수
      totalRow: 0,  // 전체 row 
      rowPerPage: 10,  // 한 페이지 당 row (디폴트 : 10)
      startPage: 0, // 페이지 범위 내 시작페이지
      endPage: 0, // 페이지 범위 내 종료페이지
      firstPage: 1, // 최초 페이지
      lastPage: 0,  // 최종 페이지
      slicedTodo: [], // 한 페이지에 출력되는 게시글 목록
    }
  },
  
  async mounted () {
    // await this.loadData();
    await this.init();
  },
  methods: {
    async init () {
      await axios({
        method: 'get',
        url: 'https://jsonplaceholder.typicode.com/todos',
      })
        .then(response => {
          console.log('API 데이터 결과', response);
          const result = response.data.filter((item) => {
            return item.id <= 183 // 총 Row수 : 183개
          })

          this.todoList = result;         // API 데이터 셋
          this.totalRow = result.length;  // 총 ROW 수
        })
        .catch(err => {
          console.log(err);
        })
      
      this.firstPage = 1; // 최초페이지

      this.pageNum = 1; // 현재페이지
    },
    // 페이지 설정
    refreshPagination (currentPage, pageRow, totalRow) {
      this.totalPageCnt = Math.floor(this.totalRow / this.listSize);
      if (totalRow % this.listSize > 0) this.totalPageCnt += 1; // 19      

      this.lastPage = this.totalPageCnt;  // 최종페이지
      
      // 페이지 범위 설정 로직
      if (currentPage - 5 <= 0) { // 5페이지 이하의 경우
        this.startPage = 1;
        this.endPage = this.pageRange;
      } else if (currentPage + 5 > this.totalPageCnt) { // 15페이지 이상의 경우
        this.startPage = this.totalPageCnt - this.pageRange + 1;   
        this.endPage = this.startPage + this.pageRange - 1;
      } else {
        this.startPage = currentPage -4;
        this.endPage = currentPage + 5 ;
      }

      // 페이지배열 범위 지정
      this.emptyPageArr();
      for (let page=this.startPage; page<=this.endPage; page++) {
        this.makePageArr(page);
      }
      
      const start = (currentPage - 1) * this.listSize;
      const end = start + pageRow;

      this.paginatedData(start, end)

      console.log('** OUPUT **');
      console.log(`현재 페이지 : ${this.pageNum}`);
      console.log(`총 페이지 수 : ${this.totalPageCnt}`);
      console.log(`시작 페이지 NO: ${this.startPage}`);
      console.log(`종료 페이지 NO: ${this.endPage}`);
      console.log(`이전 페이지 이동 가능 여부: ${this.pageNum !== this.firstPage}`);
      console.log(`다음 페이지 이동 가능 여부: ${this.pageNum !== this.lastPage}`);
      console.log('==============END================')
      console.log('\n');
    },

    // 해당 페이지 게시글 목록 추출
    paginatedData (start, end) {
      this.slicedTodo = this.todoList.slice(start, end);
    },
    // 다음 페이지로 이동
    nextPage () {
      this.pageNum += 1;
    },
    // 이전 페이지로 이동
    prevPage () {
      this.pageNum -= 1;
    },
    // 페이지배열(pageRange) 초기화
    emptyPageArr () {
      this.pageArr = [];
    },
    // 페이지배열 생성
    makePageArr (index) {
      this.pageArr.push(index);
    },
    // 특정페이지로 이동
    changePage(pageNumber) {
      this.pageNum = pageNumber;
    }
  },
    watch: {
    // pageNum이 변경될 때마다 실행
    pageNum (newPage) {
      let pageRow;  // 페이지 당 row수
      if (newPage === this.lastPage) {
        pageRow = this.totalRow % this.listSize === 0 ? 
                  10 : this.totalRow % this.listSize ;
      } else {
        pageRow = 10;
      }

      console.log('==============START================');
      console.log('** INPUT **')
      console.log(`현재 페이지  : ${newPage}page`); 
      console.log(`페이지 당 Row수 : ${pageRow}`);
      console.log('총 Row 수 ', this.totalRow);
      console.log('\n');

      this.refreshPagination(newPage, pageRow, this.totalRow);
    },
  },
}
</script>

<style>
table {
  width: 100%;
  border-collapse: collapse;
}
table th {
  font-size: 1.2rem;
}
table tr {
  height: 2rem;
  text-align: center;
  border-bottom: 1px solid #505050;
}
table tr:first-of-type {
  border-top: 2px solid #404040;
}
table tr td {
  padding: 1rem 0;
  font-size: 1.1rem;
}
.btn-cover {
  margin-top: 1.5rem;
  text-align: center;
}
.btn-cover .page-btn {
  width: 5rem;
  height: 2rem;
}
.btn-cover .page-count {
  padding: 0 1rem;
}
.btn-cover .each-page {
  width: 3rem;
  height: 2rem;
  font-size: 1.1rem;
  margin-left: 0.1rem;
  margin-right: 0.1rem;
}
.page-active {
  background-color: blue;
  color: white;
}
</style>

PaginatedList.vue 컴포넌트입니다.

 

1. 컴포넌트가 mounted 된 이후 init 메서드를 호출하여 API 데이터 셋을 가져오고 todoList라는 배열에 저장합니다. 그리고 페이징에 필요한 총 row 수,최초페이지, 현재페이지를 설정합니다.

2. 현재페이지가 설정되면 watch속성에 의해 refreshPagination 메서드가 작동합니다. 매개변수로는 유저가 이동하려는 페이지(newPage), 해당 페이지에서 출력할 게시글 개수(pageRow), 그리고 전체 게시글의 개수(totalRow)를 보내줬습니다. pageRow는 기본적으로 10으로 설정하여 한 페이 당 10개씩 게시글이 출력되도록 하고 마지막 페이지에서만 조건문을 통해 10으로 나누고 나서도 남아있는 게시글을 출력하도록 만들었습니다.

3. refreshPagination 메서드에서는 전체 게시글 개수(totalRow)와 한 페이 당 출력할 게시글 개수(listSize) 통해 전체 페이지 개수(totalPageCnt)를 먼저 계산합니다. 그리고 페이지 범위를 설정하여 유저가 어떤 페이지에 머물든 10페이지가 보이도록 만들었습니다.

4. changePage 혹은 nextPage, prevPage를 통해 pageNum(현재 페이지)를 변경하면 watch 함수를 통해 페이징 처리가 다시 이루어지고, 변경된 데이터들을 기반으로 관련된 Html 요소들이 재렌더링 되면서 각 페이지에 맞는 게시글들을이 업데이트 되는 것을 볼 수 있습니다.