사용자들이 급식에 관해 얘기하고 본인의 식습관에 따라 얻은 캐릭터를 자랑할 수 있는 SNS기능을 구현해야 했다.
모바일 환경을 가정하고 화면을 구성하는 만큼 무한스크롤을 통해 UX를 높이고 싶었다.
당시엔 비교적 여유가 있어 다음 두 가지 방법으로 구현해보고 더 나은 것을 취사선택 하기로 했다. 일단 이 글에선 두 번째 방법만 소개할까 한다.
- 사용자의 scroll이벤트를 인식해 일정 높이까지 height가 내려가면 새로운 정보를 받아온다.
- Intersection Observer API를 활용해 사용자의 브라우저 화면이 마지막 글에 접근하면 새로운 정보를 받아오기
const getMoreList = async () => {
// 마지막 글을 봤다면 더 이상 요청을 보내지 않는다.
if (boardInfo.lastBoardId === 1) {
return;
}
// 처음 방문하거나 사용자가 글 목록의 끝에 도달한다면 발동될 함수
await getBoardList().then((data) => {
if (articles[0]?.id === -1) {
setArticles(data);
} else {
setArticles([...articles, ...data]);
}
});
};
통신을 위한 함수를 선언한다. 그리고 통신에 성공하면 2가지 경우로 나뉜다.혹시나 첫 통신이라면 에러방지용 static 배열을 교체하고 아니라면 가져온 정보들을 render할 배열의 뒤에 붙여넣는다.
그리고 받아온 데이터가 만약 너무 작아 뒤에서 설정할 조건에 계속해서 걸리거나 마지막 글을 체크했다면 더 이상 통신을 시도하지 않는다.
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(async (entry) => {
if (entry.isIntersecting) {
await getMoreList();
}
});
},
[observerOptions, articles]
);
observer.observe(articleListRef.current.lastChild);
Intersection Observer API를 활용하여 마지막 글에 Observer를 붙인다. 이 옵저버에 사용자의 브라우저가 접근하면 미리 준비해뒀던 통신 함수를 사용할 수 있다.
const observerOptions = {
root: null,
rootMargin: "100px",
threshold: 0.5,
};
또한 위와 같은 option을 조절하여 원하는 타이밍에 함수가 작동하게도 할 수 있다. 각각의 효능(?)은 다음과 같다.
좀 더 자세한 걸 알고싶다면 언제나 그렇듯 MDN 공식문서를 추천한다.
useEffect(() => {
// 통신함수
// Observer에 부착될 함수
observer.observe(articleListRef.current.lastChild);
return () => observer.disconnect();
}, [articles, boardInfo]);
마지막으로 위에서 선언한 함수들을 모두 useEffect안에 넣어둔다.
게시글을 가져온다면 마지막 글에 Observer를 부착해두고 다시 발동한다면 떼어내 중복으로 요청되지 않도록 한다.
마지막 글을 봤을 때 사용자는 왜 더 이상 글이 나오지 않는가 의문이 들거라 생각했다. 페이스북이나 트위터 같이 마지막 글에서 아래로 스크롤 하면 로딩 아이콘을 보여주고 싶었고, 마찬가지로 위로 스크롤하면 새로고침을 하게도 하고 싶었다. 하지만 아쉽게도 시간이 부족해 미처 구현하지 못해 UX적인 측면에서 아쉬움이 있다.
또한 dependencyArray에 useEffect 내부에서 set하는 state가 들어있어 불필요하게 2번 발동할 거라 생각한다. 이 점 또한 개선해야할 부분이라 생각한다.
6주 동안 눈 코 뜰 새 없이 바빴다고 생각했는데 돌아보니 그냥 시간을 허비했다는 느낌이 많이 드는건 왤까. 이미 시행착오를 다 알고 있어서 과거의 내가 좀 더 빠르게 일하길 바라는 꼰대 마인드가 아닐까?
암튼 다음 글은 모바일과 데스크탑 환경에서의 사진 저장이 될 것 같다.