예전 프로젝트에서 Infinite Scroll을 구현한 적이 있습니다. 까먹을까봐 정리하고 가겠습니다.
Infinite Scroll이란?
페이스북이나 인스타그램의 피드를 본다고 생각해봅시다. 게시글이 만약 400개가 있는 계정이라고 하면, 과연 인스타그램은 그 400개를 다 불러올까요? 그렇게 되면 서버 로드도 엄청나고, 게시글 사진 그리는데 시간이 너무 많이 들게 됩니다. 따라서 우리는 페이지네이션을 통해서 데이터들을 나눠서 불러오게 됩니다.
이 페이지네이션의 구현 방법은 버튼식, 스크롤식 등이 있는데요. 버튼식은 일반 커뮤니티에서 '다음' 누르면 다음 10개의 게시글이 뜨는 방식입니다. 스크롤식은 스크롤을 끝까지 내리면 자동으로 다음 n개를 불러오는 방식입니다.
Infinite Scroll은 페이지 하단에 스크롤이 도착하면 자동으로 새로운 콘텐츠가 로드되는 방식입니다. 페북이나 인스타그램에는 이 Infinite Scroll이 적용되어 있습니다.
Ref란?
스크롤의 감지는 DOM 엘리먼트에 직접 접근하여 구현할 수 있습니다. 일반적인 state와 props로는 불가능한 일이지요. 우리는 Ref를 이용하여 DOM element에 직접 접근할 수 있습니다. Ref를 이용하는 경우는 다음과 같습니다.
- 인풋에 포커스를 주기, 미디어 재생 등 엘리먼트에 직접 접근하고 싶을 때
- 애니메이션을 직접 실행할 때
- DOM 라이브러리를 React와 같이 사용할 때
Ref는 useRef라는 훅을 통해 사용할 수 있으며, JSX단에서 노드에 ref 속성을 지정함으로써 사용할 수 있습니다.
const App = () => {
const myRef = useRef(null);
return (
<div ref={myRef}>
Test
</div>
);
};
export default App;
위의 코드처럼 ref 속성을 사용할 수 있습니다. useRef 훅을 통해 생성됩니다. useRef는 인자로 받은 값을 current 속성에 할당하고, DOM에 저장합니다. Infinite Scroll에는 사용하지 않았지만, 이 객체에는 mutable한 값을 넣어 관리할 수 있습니다. 렌더링되어도 값이 유지되는 하나의 저장소로 활용할 수 있습니다.
다만, Ref를 남용하는 것은 권장되지 않습니다. 공식문서에서도 이를 언급하고 있습니다. 선언적으로 해결될 수 있는 문제에는 Ref를 사용하기보다는 상위 컴포넌트의 state를 이용하는 것이 더 좋다는 의미이죠.
Ref를 이용해 Infinite Scroll 구현하기
기본적인 틀은 다음과 같습니다.
onScroll 이벤트로 스크롤 트리거 -> Ref 객체로 스크롤이 끝에 닿았는지 확인 -> 만약 도달했다면 다음 페이지를 서버에서 불러옴 -> 원래 있던 배열과 새로 불러온 배열을 합치고 setState
여기서 Ref로 창의 스크롤 위치를 불러와서 어떻게 '끝에 닿았는지' 확인하는 과정이 필요합니다.
scrollHeight는 화면 밖의 높이를 포함한 페이지의 총 높이입니다.
scrollTop은 이미 스크롤되어서 위로 올라가버려 보이지 않는 부분의 높이입니다.
clientHeight는 사용자에게 현재 보여지고 있는 부분의 높이입니다.
따라서 이미 올라간 높이+현재 보여진 높이 >= 페이지 총 높이라면 사용자가 페이지 끝에 도달한 것이 되겠죠!
즉, ref에서 위의 세개의 값을 불러온 뒤, scrollTop+clientHeight >= scrollHeight인 경우 다음 데이터들을 불러오면 되겠습니다. 여기서 저는 사용자 경험을 위해 scrollTop이 약간 부족하더라도 미리 데이터를 불러와 부드러운 Infinte Scroll이 구현되게끔 하였습니다.
import {useRef} from 'react';
const scroller = useRef();
const handleScrill = () => {
if(
scroller.current.scrollHeight -
scroller.current.scrollTop -
scroller.current.clientHeight < 300 &&
nextPage
) {
pagination();
}
};
const pagination = () => {
//axios 이용해서 받아오는 Promise
.then((res) => {
if(res.data.next) { setNextPage(~~) }
if(nextPage) {
setCommentList([...commentList, ...response.data.data]);
}
});
};
위와 같이 구현하였다.
참고문헌
'Me > FrontEnd' 카테고리의 다른 글
CI/CD가 무엇일까? (0) | 2022.06.21 |
---|---|
[material-ui] ~~~ was not found in '@mui/material' 오류와 import level (0) | 2022.06.04 |
프로세스와 스레드의 차이 (0) | 2022.05.20 |
[JavaScript] JS가 싱글스레드임에도 비동기 처리가 가능한 이유 (0) | 2022.05.19 |
[JavaScript] Promise와 async/await (0) | 2022.05.16 |
댓글