React에서 Infinite Scroll 구현하기

허정민
10 min readOct 21, 2018

--

이번 시간에는 요즘 많은 사이트들에서 흔히쓰는 Infinite스크롤에 대해서 알아보려고 합니다. 제가 최근에 프로젝트를 하나 하고 있는데 Front-end를 맡게 됐습니다. 페이지의 여러 부분을 같이 담당하고 있지만 우선은 메인 페이지 구현에 힘을 쓰고 있습니다.

우선 fake이미지 데이터를 블록마다 할당해 주었다.

일단 메인 페이지에는 추후에 더 많은것이 들어가겠지만, 핵심 기능은 서버에서 받아온 유저들의 포스트 데이터를 보기 편하게 블록형태로 뿌려주는 것입니다. 일단 처음에는 서버에 있는 모든 포스트를 다 가져오는 형태로 구현을 해봤습니다. 받아온 사진이 모두 뜨는 것을 확인했고, 이후 본격적으로 Infinite 스크롤을 구현하기로 했습니다.

Infinite 스크롤이란?

Infinite 스크롤은 페이스북을 떠올리시면 됩니다. 사이트를 맨끝까지 스크롤 하면 새로운 포스트가 랜더되는 형태를 Infinite 스크롤이라고 합니다. 페이스북이 가장 대표적이지만 요즘 많은 사이트들에서 사용하고 있습니다. 그래서 저도 메인 페이지의 불필요한 포스트를 모두 긁어오기 보다는 한번에 20~30개 정도의 포스트만 가져와서 스크롤 될때마다 포스트를 새로 받아오는 방식으로 짜보려고 합니다.

1. 서버에서 지정된 숫자만큼의 데이터 받아오기

우선 제가 썼던 fake image data는 한번에 모든 리스트를 뿌려주는 api 라서 엄밀히 말하면 데이터를 원하는 갯수만큼 뿌려주지는 못합니다. 이미 모든 포스트를 다 뿌려 줄 수만 있는 api이기 때문에 저희가 구현하려는 원하는 갯수만큼만 받아오는 것은 사실 애초에 불가능합니다. 그래도 최대한 비슷하게 구현을 해보기 위해 get 요청으로 받아온 data를 분절하여 리턴을 시켜주었습니다.

받아온 데이터를 슬라이스하여 일부만 가져오는 함수를 짜봤다.

그리고 위에 보이는 스테이트 중에 preItems와 items를 정해줘봤는데요 초기값을 보시면

한번에 20개의 데이터를 가져오기 위해 초기값을 설정했다.

20으로 설정해놓은것을 볼 수 있습니다. 여기서 items와 preItems를 나눈 이유는 데이터를 겹치지 않게 20개씩만 가져오기 위해서 입니다. 초기에는 20개를 가져오지만 스크롤이 마지막에 도달하면 다시 그 다음 20개를 가져오기 위해서 만들었습니다. 그냥 20을 더해주고 빼주는 것으로도 구현 가능해보였지만 뭔가 값은 정해져있는게 더 안전할거란 나름의 믿음(?)에 근거하여 전 아이템값과 20이 더해진 아이템 값을 따로 설정해주고 리스트를 20개씩 잘라서 리턴해주는 함수에서 슬라이스를 하는 부분에 넣어주었습니다.

20개가 우선 들어와 있는걸 확인할 수 있다.

콘솔을 찍어보니 잘 들어와있는걸 확인할 수 있습니다. 이제 서버로부터 20개씩 받아오는 것은 비슷하게 만들었으니, 스크롤을 맨끝까지하면 20개를 더 불러와서 보여주는 것을 구현해야겠죠? 그럼 우선 무엇부터 해야할까요? 여기서 고민이 많이 됐지만 왠지 화면의 길이를 구해서 그 끝에 도달하면 새로운 데이터를 불러와서 랜더 시키는 것이 가능할 것이라는 생각이 들었습니다.

2. 페이지의 기하학(Geometry) 속성값

우선 제가 화면 높이를 어떻게 구했는지를 말하기전에 높이와 관련된 속성값들에 저희가 접근할 수 있다는 것을 먼저 알려드리고 싶습니다. 위의 페이지를 참고하면 화면의 가로길이, 세로길이 스크롤된 길이 등 다양한 수치들을 어떻게 접근하며 서로 어떻게 다른지를 알기쉽게 설명해놨습니다. 지금 부터 이야기할 내용을 먼저 읽기전에 위 사이트를 먼저 읽고오시면 훨씬 이해하기가 쉬울 겁니다.

a. 스크롤 길이와 화면길이

블로그 글을 읽어보셨다면 document.documentElement에 접근하면 html geometry값에 접근이 가능하다는 것을 아셨을 겁니다. 그 중에서 화면을 구성하는 여러가지 값들이 있다는 것을 보셨을 겁니다. 이중에서 저희는 어떤걸 가져다 쓰면될지를 한번 알아보도록 하겠습니다.

geometry값을 알기쉽게 정리해놓은 이미지 출처:javascript.info

우선 그림을 참고하면 저희는 세로값만 필요하니 width에 관련된 값은 고려를 안해도 될거 같습니다. width와 관련된 친구들은 과감히 빼고 세로와 관련된 값만 한번 정리를 해보도록 할까요? 대충보아도 clientHeight, offsetHeight, scrollHeight, scrollTop, offsetTop 정도가 필요해 보이네요. 이 친구들의 값을 잘 참조하면 화면의 끝값에서 데이터를 새로 받아와서 뿌려주는게 가능해 보입니다! 그럼 콘솔을 찍어보면서 한개 씩 알아볼까요?

하나씩 콘솔을 찍어서 나온 값

우선 스크롤을 할때마다 우리가 원하는 세로길이에 관련된 모든 값을 콘솔로 찍어주는 함수를 만들어봤습니다. 우선 눈이 띄는건 클라이언트 탑과 오프셋탑이 둘다 값이 0으로 나온다는 겁니다.

오프셋 탑은 간단히 말해서 왼쪽 끝 맨위를 기준으로 한 위치값이라고 보시면 편할거 같습니다. 우리는 고정된 div안에 받아온 데이터를 block형태로 뿌려주기 때문에 이를 담는 container div의 오프셋값은 탑이든 레프트든 0으로 고정된 것이죠. 왜냐면 위치값을 전혀 부여하지 않았기 때문이죠. 이로서 오프셋 탑 값은 제 상황에서는 굳이 고려를 안해도 될 것으로 보입니다.

그럼 다음으로 클라이언트 탑을 알아보죠. 클라이언트 탑은 클라이언트와 오프셋의 사이에 있는 경계선 정도로 보면 될거 같습니다. 하지만 저희는 앞서 말했다 시피 위치를 정하지 않았고 따라서 클라이언트와 오프셋간의 차이가 없다고 보면 될거 같습니다. 그래서 똑같이 0값을 반환하는 것으로 보입니다. 이 추론을 뒷받침 해주는건 오프셋 높이와 클라이언트 높이가 같다는 것에서 가능해 보입니다. 그래서 두 탑값은 현재 쓰는게 큰 의미는 없어 보입니다.

나머지를 이제 알아볼까요? 스크롤 높이는 클라이언트 화면에 보이지 않는 곳까지의 총 길이를 의미합니다. 위의 그림예제를 보면 쉽게 알 수 있죠? 쉽게 말해서 스크롤로 저희가 올린 곳, 그리고 곧 스크롤하여 내려갈 곳에 현재 저희가 보고있는 곳까지 합쳐서 스크롤 높이인것입니다. 사이트의 총길이라고 봐도 괜찮을것 같습니다. 그 다음은 스크롤 탑인데 이 친구는 스크롤되어 올라간 만큼의 높이라고 보면 됩니다. 저희가 스크롤해서 올리면 클라이언트에서는 보이지 않는 이미 올라가버린 구간이 있겠죠? 그 구간이 바로 스크롤 탑이라고 보면 됩니다.

클라이언트 높이와 오프셋 높이는 현재 같은데 그 이유는 앞에 설명을 드렸죠? 이 친구들은 현재 보여지는 화면의 높이라고 생각하시면 편합니다. 지금 딱 보고있는 화면의 그 높이인거죠. 그래서 한번 정해지면 고정이라고 보면 편할거 같습니다.

b. 스크롤높이와 스크롤탑 그리고 클라이언트 높이를 이용한 페이지 끝 구하기

고정값도 있고 스크롤을 할때마다 변하는 값도 있고 뭔가 실마리가 점점 잡히는 느낌이죠?

스크롤할때마다 스크롤높이와 스크롤탑값이 변하고 있다.

콘솔을 찍어보면 아시겠지만 클라이언트의 높이는 고정값입니다. 말했다시피 저희에게 보여지는 딱 그만큼이기 때문이죠. 스크롤탑과 스크롤 높이는 계속 변하고 있습니다. 그러면 화면 끝을 어떻게 계산할까요? 처음에 들었던 생각은 그냥 스크롤 높이를 검사해서 끝에 다다르면 데이터를 더 불러오면 되지 않을까 생각했지만, 생각해보니 이 함수는 1번 밖에 사용하지 못합니다. 왜냐햐면 20개를 더 불러오면 총길이가 또 변해버리기 때문이죠. 그렇다면 계속 변화하는 총길이에 대응을 어떻게 해야할까 생각해보니 답이 나왔습니다. 스크롤을 해서 올린값과 지금 현재 화면의 값이 화면의 총길이와 같을때 데이터를 불러오면 해결되지 않을까?

스크롤탑과 클라이언트 높이의 값이 스크롤 높이와 같으면 그것은 바로 페이지의 끝값!

그래서 간단하게 if문을 써봤습니다. 그런데 참고했던 사이트에서 읽었던 글이 떠올랐습니다. 만약 사이트 전체를 컨트롤하는거라면 document.documentElement의 값만을 참조하는 것은 위험할 수 있다는 것이었습니다. 그래서 이 값을 좀더 정확하게 참조를 하려면 body에 담겨있는 geometry값과 비교를 해야한다는 것이었죠. 솔직히 왜 이게 달라지는 지는 저도 아직 잘 모르겠습니다. 좀 더 공부가 필요해보입니다. 하지만 그럴 수 있다고 하니 일단 body안에 있는 geometry도 검사를 해보겠습니다.

바디 스크롤 높이는 같았지만 바디 스크롤 탑이 계속 0값이었다.

검사를 해본결과 스크롤 높이는 둘이 똑같고 바디 스크롤 탑만 0값으로 고정이 돼있었습니다. 왜 바디 스크롤 탑은 0인지는 더 공부를 해서 정리를 해보도록 하겠습니다. 참조했던 블로그에서는 만약 이 둘의 값이 다르면 가장 큰값을 따르는것이 가장 안전하다고 봤던거 같습니다. 그래서 Max 함수를 돌려서 둘중 가장 큰값을 뽑아내는걸로 검사를 한번 더 돌리기로 합니다.

완성된 infinite 스크롤 함수

그래서 완성한 Infinite Scroll 함수를 보면 클라이언트 높이와 스크롤 탑의 값의 합이 스크롤 높이의 값과 같으면 그것이 스크롤의 끝에 다다른 것이라고 계산을 하고 이때 아까 정해준 스테이트의 값을 업데이트 시켜줍니다. 그리고 get url 함수를 다시 한번 실행시켜줘서 서버로부터 데이터를 받아오죠. 그럼 이렇게 만든 함수는 어디서 실행을 시켜줘야 할까요? 저는 이곳이 메인페이지 였기 때문에 특정 엘리멘트의 스크롤에 대응하는게 아니라 페이지 전체에 적용이 되어야 했습니다. 전역으로 적용된다는 느낌으로 말이죠. 그래서 윈도우 이벤트 리스너를 사용했습니다.

componentDidMount가 되면 윈도우 전체에 스크롤 이벤트를 실행시켜 주었다.

자 이제 실행이 되나 확인 해볼까요?

스크롤 끝에 도달하면 새로운 데이터를 받아오는 모습

안정적으로 잘 실행이 됩니다!! 저 처럼 구현하는 것 말고도 다른 방법이 많겠지만 나름 조사를 해서 제가 잘 이해한 방법을 써서 한번 구현을 해봤습니다. 오늘 블로그로 정리하면서 헷갈렸던 부분봐 미진했던 부분은 좀더 보완을해서 빠른 시일내에 올려보도록하겠습니다.

--

--