2~3주간 진행했던 개인 프로젝트는 얼추 마무리가 되었다.
포트폴리오 사이트를 만드는 과정만 남았다. 또한
면접에 필요한 기술면접 또한 공부를 해야한다.
그 전에 어떤 프로젝트에 쓰인 코드를 정리를 하려고 한다. 기억보단 기록이 좋으니까
웹, 모바일에서 빠질 수 없는 기법이지 아닐까 싶다. 말 그대로 무한 스크롤링인데 많은 데이터를
한번에 불러올 경우 로딩 시간이 길어져서 사용자에게 불편함을 느끼게 한다. 기획에 맞는 몇개의 데이터를 불러온 뒤 스크롤을 하면 새로운 데이터를 불러오는 기법이다.
인피니트 스크롤링을 만드는데 있어서 스크롤 이벤트를 사용했다. 아마 처음 인피니트 스크롤링을 접할 때 가장 흔히 접하는 방법인 것 같다. 이도저도 말고 바로 코드를 기록하겠다.
// pages/index.tsx
import React, { useEffect } from 'react';
import { postsLoad } from '../actions/post';
const Home = () => {
const { loadPostsMore, loadPostsLoading } = useSelect((state) => state.post)
useEffect(() => {
const infiniteScroll = throttle(() => {
if (window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300) {
// loadPostMore true / loadPostsLoading false면 dispatch
if (loadPostsMore && !loadPostsLoading) {
dispatch(
postsLoad({
lastId,
}),
);
}
}
}, 200);
// 스크롤 이벤트 발생
window.addEventListener('scroll', infiniteScroll);
return () => {
// 메모리 누수로 인한 스크롤 이벤트 지우기
window.removeEventListener('scroll', infiniteScroll);
};
}, [loadPostsMore, loadPostsLoading, lastId, dispatch]);
}
(a) window.scrollY
- 스크롤을 얼마나 내렸는지에 대한 값
(b) document.documentElement.clientHeight
- 현재 화면에 보이는 높이 값
(c) document.documentElement.scrollHeight
- 페이지의 총 높이 값
이 세가지를 사용해서 인피니트 스크롤링을 구현하였다.
간단하게 식을 풀자면, (a) + (b) > (c)-300 의 식이 된다.
(c)의 총 길이가 1000이고, 여기서 -300을 해준다. 그럼 (c)는 700이다.
그럼 (a) + (b)가 700이면 dispatch를 하게된다.
이런 방법으로 하게되면 원하는 위치에서 dispatch를 할 수 있다. 그냥 이렇게만 한다면 저 스크롤위치에서 엄청난 양의 dispatch를 하게된다. 당연히 성능 저하에 악영향을 끼치게 된다.
그래서 lodash 라이브러리의 throttle
을 사용했다.
lodash
모듈성, 성능 및 기타 기능을 제공하는 최신 JavaScript 유틸리티 라이브러리. 라고 파파고에서
번역을 해주는데~ 자바스크립트로 사용되는 메소드를 lodash를 통해서 간편하게 사용할 수 있다.
공식문서 참고
그 중 사용 된 throttle
은 콜백함수와 시간을 정할 수 있는데, 시간을 1000(1초)로 정해두고
스크롤을 하게된다면, 1초안에는 반드시 1번만 콜백함수를 실행한다. 이 기능을 사용해서 dispatch가
불필요하게 실행되는 부분을 방지했다.
마지막은 연속 두번을 불러온 것 같지만 제대로 불러온건 맞다..
스크롤로 인해서 실행되는 불필요한 디스패치를 방지했다고 끝은 아니다.
조건에 맞게 디스패치 해서 액션이 실행이 되야한다.
if (loadPostsMore && !loadPostsLoading) {
dispatch(
postsLoad({
lastId,
}),
);
}
loadPostsMore
- 포스트를 불러올 수 있는 상태
loadPostsLoading
- 포스트를 불러오는 중
위 조건으로 진행을 했는데 loadPostsMore
는 불러올 수 있는 포스트가 남아있다면 계속 true
상태를 유지하게 된다.
loadPostsLoading
는 포스트를 back서버로 요청해서 불러오는 중 pending
상태를 의미한다. 기본 상태를 true
지만, 포스트를 불러올 경우 fulfilled
가 되면 false
로 상태 변경을 하기 때문에 두개의 조건이 충족되기 때문에 포스트를 불러오게 된다.
lastId는 어떻게 사용될까?
lasId는 현재 불러온 포스트들 중 마지막으로 불러온 포스트의 id값을 갖고 있다.
이 값을 back 서버에 요청할 때 쿼리스트링으로 보낸다. back 서버에서는 어떻게 진행되는지 알아보자.
최초 로딩되어 아래 router가 실행되면, 쿼리스트링으로 0을 보내게된다. 이건 기본값으로 스크롤링을 하게되면 위에서 lastId의 값을 쿼리스트링으로 전달한다. 그럼 router에서는 쿼리스트링이 0보다 클 경우 스크롤링으로 간주하여, lastId보다 작은 id의 포스트를 불러온다.
// back/routes/posts
router.get('/', async (req, res, next) => {
try {
// 초기 조건은 없는 상태
const where = {};
// req.query가 0보다 클 경우 (즉, 스크롤링 시)에 where에 조건 추가
if (parseInt(req.query.last, 10) > 0) {
// 마지막 포스트의 id값 보다 작은 포스트를 불러오기
where.id = { [Op.lt]: parseInt(req.query.last, 10) };
}
// 최초 로드일 때는 order 순으로 9개를 불러온다.
// 추후 스크롤링 시에는 마지막 id보다 작은 포스트를 order순으로 9개를 불러온다.
const post = await Post.findAll({
where,
order: [
['createdAt', 'DESC'],
],
limit: 9,
// ...
});
res.status(200).json(post);
} catch(error) {
console.error(error);
next(error);
}
});
이렇게 스크롤 이벤트를 사용하여 인피니트 스크롤링을 구현하였다.
스크롤 이벤트는 좋은 방법이 아니라고는 하는데... 시간이 많다면 더 나은 방법으로도 구현하고 싶다.