1. last Id 설정
useEffect(() => {
function onScroll() {
if (window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300) {
if (hasMorePosts && !loadPostsLoading) {
const lastId = mainPosts[mainPosts.length - 1].id;
dispatch({ type: LOAD_POSTS_REQUEST, lastId });
}
}
}
window.addEventListener('scroll', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
};
}, [hasMorePosts, loadPostsLoading]);
- onScroll 함수는 스크롤 이벤트가 발생할 때 호출되는 콜백 함수입니다.
- 조건문을 통해 현재 스크롤 위치(window.scrollY)와 화면에 보이는 부분의 높이(document.documentElement.clientHeight)를 합한 값이 문서의 전체 높이(document.documentElement.scrollHeight)에서 일정 값(여기서는 300)을 뺀 값보다 크면, 스크롤이 페이지 하단에 도달했다고 판단합니다.
- 만약 추가적인 포스트를 로드해야 하는 상황이면(hasMorePosts가 true이고 loadPostsLoading이 false인 경우), 마지막으로 로드된 포스트의 ID를 추출하고 이를 서버에 전달하여 더 많은 포스트를 요청합니다.
- dispatch({ type: LOAD_POSTS_REQUEST, lastId }); 코드는 Redux의 action을 dispatch하여 상태를 업데이트하는 역할을 합니다.
마지막으로, useEffect 내부에서는 window.addEventListener를 사용하여 스크롤 이벤트를 감지하고, 컴포넌트가 언마운트될 때 이벤트 리스너를 정리하기 위해 return 문에서 window.removeEventListener를 호출합니다.
2. saga 작성
API
function loadPostsAPI(lastId) {
return axios.get(`/posts?lastId=${lastId || 0}`);
}
- Axios를 사용하여 서버로부터 게시물을 가져오는 API 호출을 수행하는 함수입니다. 함수는 axios.get을 사용하여 서버의 /posts 엔드포인트로 GET 요청을 보내고, lastId라는 파라미터를 이용하여 마지막으로 로드된 게시물의 ID를 서버에 전달합니다.
- 게시글의 갯수가 10개를 넘기지 않아 undefined 를 가져올 경우를 대비해 || 0 으로 에러 방지
3. routes 에서 last Id 받기
// Get /posts
router.get('/', async (req, res, next) => {
try {
const where = {};
// 초기 로딩이 아닐 경우 실행.
if (parseInt(req.query.lastId, 10)) {
where.id = { [Op.lt]: parseInt(req.query.lastId, 10) };
}
const posts = await Post.findAll({
where,
limit: 10,
order: [
['createdAt', 'DESC'],
[Comment, 'createdAt', 'DESC'],
],
include: [
{
model: User,
attributes: ['id', 'nickname'],
},
{
model: Image,
},
{
model: Comment,
include: [
{
model: User,
attributes: ['id', 'nickname'],
order: [['createdAt', 'DESC']],
},
],
},
{
model: User, // 좋아요 누른 사람
as: 'Likers',
attributes: ['id'],
},
{
model: Post,
as: 'Retweet',
include: [
{
model: User,
attributes: ['id', 'nickname'],
},
],
},
{
model: Post,
as: 'Retweet',
include: [
{
model: User,
attributes: ['id', 'nickname'],
},
],
},
],
});
res.status(200).json(posts);
} catch (err) {
console.error(err);
next(err);
}
});
- const where = {};: where라는 빈 객체를 생성합니다. 이 객체는 Sequelize의 쿼리에서 사용될 조건을 나타냅니다.
- if (parseInt(req.query.lastId, 10)) {: 요청의 쿼리 파라미터 중 lastId가 있는지 확인합니다. 존재한다면, 조건문이 참이 되어 이후의 코드 블록이 실행됩니다.
- where.id = { [Op.lt]: parseInt(req.query.lastId, 10) };: where 객체의 id 속성에 Sequelize의 [Op.lt] 연산자를 사용하여 "작은 값"을 나타내는 객체를 할당합니다. 이로써 해당 ID보다 작은 게시물만 검색하게 됩니다.
- const posts = await Post.findAll({ ... });: Post 모델을 사용하여 데이터베이스에서 게시물을 조회합니다. findAll 메서드를 사용하며, 앞서 설정한 where 조건에 따라 적절한 게시물을 검색합니다.
- res.status(200).json(posts);: 성공적으로 조회된 게시물을 클라이언트에게 JSON 형식으로 응답합니다. HTTP 상태 코드는 200(OK)입니다.
- } catch (err) { console.error(err); next(err); }: try 블록에서 예외가 발생하면 catch 블록이 실행됩니다. 에러를 콘솔에 출력하고, next(err)를 호출하여 에러를 다음 미들웨어로 전달합니다.
쿼리스트링에 따라 조회할 데이터가 달라지기 때문에 where 라는 빈 객체를 만들어 조건문으로 쿼리스트링 값을 넣어주었다.
문제발생: 계속해서 조회하는 무한조회 등장
총 14개의 게시글을 모두 조회했지만, 트리거가 작동되면, 계속해서 api 조회로 게시글들을 불러오는 문제가 발생.
해결 노력
case LOAD_POSTS_SUCCESS:
draft.loadPostsLoading = false;
draft.loadPostsDone = true;
draft.mainPosts = action.data.concat(draft.mainPosts);
draft.hasMorePosts = action.data.length === 10;
break;
hasMorePosts 라는 불리언 상태값을 만들어 불러온 게시글의 갯수가 10개라면, true 그렇지 않은 경우는 routes 에서 limit 을 10 개로 설정해두었기 때문에 미만일 수 밖에 없어 false 로 상태를 변경시켜 더 이상 조회를 하지 않도록 하는 방법을 생각했다.
하지만, 만약 게시글의 갯수가 10의 배수였고, 딱 10개가 남은 데이터를 불러온다면, 위 조건은 마지막 게시글인 줄 모르고,hasMorePosts 를 false 로 바꿔주지 못할 것이다. 근데 그렇다하더라도 api 가 실행되면, 더 이상 불러올 데이터가 없기 때문에 한 번의 데이터 요청 낭비가 발생하겠지만, 어떤 데이터도 더 이상 조회하진 않아 고민이었던 무한조회를 막을 순 있었다.
'Next.js' 카테고리의 다른 글
[NNN]_트러블슈팅3_SSR시 쿠키 공유하기 (0) | 2024.02.24 |
---|---|
CSR 과 SSR (0) | 2024.02.24 |
[NNN]_express.static 미들웨어, 사진 미리보기 (0) | 2024.02.21 |
[NNN]_multer 로 이미지 업로드하기 (0) | 2024.02.14 |
[NNN]_credentials 로 쿠키 공유하기(Unauthuorized Error) (0) | 2024.02.09 |