슈코딩

[Django, JavaScript] 백엔드 페이지네이션 적용, 프론트 무한 스크롤 본문

개발일지/Project

[Django, JavaScript] 백엔드 페이지네이션 적용, 프론트 무한 스크롤

Roshu 2022. 7. 25. 19:45

 

이번 프로젝트에서 맡은 기능중 list 페이지에서 물품들을 나열해서 보여주는 부분이 있었다. 데이터가 적을 땐 상관없지만, 데이터가 많은 상태에서는 어떻게 보여줄까를 고민하던중 단순히 커뮤니티 게시글처럼 보여준다면, 무한 스크롤이 아닌 페이지를 나눠서 1...2...3....이렇게 적용을 시켰을것이지만, 우리가 보여주는 형식은 이미지와 밑에 간단한 내용이 보이는 형태라서 무한스크롤이 더 UI에 맞고 자연스럽다고 생각했다. 또한 핸드폰에서 어플리케이션으로 확장성까지 생각 해본다면 무한스크롤이 더 이점이 있다고 생각해서 도전해봤다. 

 

먼저 무한스크롤을 하려면 백엔드에서 데이터를 나눠서 보내줘야 할 필요가 있었다. 36개의 데이터가 있다고 생각했을 때

12개씩 나눠서 보내야 스크롤이벤트마다 호출을 통해 12개를 더불러오는 방식이다. 페이지네이션 관련된 코드는 구글링을 통해서 쉽게 찾아 볼 수 있었다. 

class PaginationHandlerMixin(object):
    @property
    def paginator(self):
        if not hasattr(self, '_paginator'):
            if self.pagination_class is None:
                self._paginator = None
            else:
                self._paginator = self.pagination_class()
        else:
            pass
        return self._paginator
    
    def paginate_queryset(self, queryset):
        if self.paginator is None:
            return None
        return self.paginator.paginate_queryset(queryset, self.request, view=self)
        
    def get_paginated_response(self, data):
        assert self.paginator is not None
        return self.paginator.get_paginated_response(data)

pagination.py라는 파일을 새로 만들어서 우선 위의 코드를 그대로 사용을한다. 그리고 나머지는 view에서 몇가지 코드만 넣으면 백엔드 작업은 끝이 나게 된다.

 

class ItemPagination(PageNumberPagination): # 👈 PageNumberPagination 상속
    page_size = 12
    
class ItemListView(APIView, PaginationHandlerMixin):
    permission_classes = [IsAddressOrReadOnly]
    authentication_classes = [JWTAuthentication]
    pagination_class = ItemPagination

    def get(self, request):
        user = request.user
        items = ItemModel.objects.filter(status="대여 가능").order_by('-created_at')
        categories = CategoryModel.objects.all()

        #유저가 주소를 설정 했을때 Query
        try:
            #시군구 까지 split해서 DB에서 쿼리 
            city = user.address.split(' ')[0]
            ward_county = user.address.split(' ')[1]
            address_query = Q(user__address__contains=city) & Q(user__address__contains=ward_county)
            items = items.filter(address_query)
        except:
            pass

        # 검색 입력값 Query Parameter로 가져오기
        search_value = request.GET.get('search', "")
        # 카테고리명 Query Parameter로 가져오기
        category_name = request.GET.get('category', "")
        # 섹션 Query Parameter로 가져오기
        section = request.GET.get('section', "")

        if search_value != "":
            search_query = Q(title__icontains=search_value)
            items = items.filter(search_query)


        if category_name != "":
            category_query = Q(category__name=category_name)
            items = items.filter(category_query)

        if section != "":
            section_query = Q(section=section)
            items = items.filter(section_query)

		#페이지네이션 관련 코드 부분
        page = self.paginate_queryset(items)

        if page is not None:
            item_serializer = self.get_paginated_response(ItemSerializer(page, many=True, context={"request": request}).data)
        else:
            item_serializer = ItemSerializer(items, many=True, context={"request": request})

        category_serializer = CategorySerializer(categories, many=True, context={"request": request})

        data = {
            'categories': category_serializer.data,
            'items': item_serializer.data,
        }

        return Response(data, status=status.HTTP_200_OK)

 

백엔드 페이지네이션에서 정말 편리한점은 다음페이지로의 url도 같이 data에 보내준다는 점이다. 그래서 나는 그걸 활용해서 스크롤마다 next: url 에 담기는 url값을 백엔드로 호출해서 다음 데이터를 가져왔다. 

 

프론트에서는 JS로 window.onsroll이벤트를 활용해서 스크롤이 가장 하단에 왔을때 함수를 불러오는 식으로 코드를 작성했다.

//스크롤 이벤트 함수
window.onscroll = function () { 
    //스크롤이 바닥에 닿았을때
    if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
        showScrollItems(); // 컨텐츠를 추가하는 함수를 불러온다.
    }
}

async function showScrollItems() {
    if (pageUrl != null) {
        const items = await scrollItemApiView(pageUrl)
        const itemsInfo = items['items']['results']
        pageUrl = items['items']['next']
        itemDataAppend(itemsInfo)
    }
}

offsetHeight 화면에서 보이는 높이를 계산해서 scrollY와 innerHeight 합이 같거나 클때 showScrollItems를 호출한다.

showScrollItems 함수에서는 pageUrl 이 null이 아닐때만 api함수를 호출하는 방식으로 했다. 데이터가 12개가 호출되고 1개라도 남아있다면 pageUrl에는 JS에서 처음 페이지를 호출한곳에서 전역변수로 url을 담아주었기 때문에 존재하면 api함수가 호출되고 아니라면 아무런 작동도 하지 않는다. 나머지는 일반적인 fetch api로 다시 데이터를 가져와서 itemDataAppend 함수를 호출해서 데이터를 붙여주었다. 실제로 작동하는것을 보면 아직 데이터가 많지않아서 인지 정말 빠르게 작동을해서 잘 구현이 된것 같아서 뿌듯했다.

 

Comments