React, redux로 게시글 무한 스크롤링을 구현
1. 서버와 연결하지 않고 프론트에서 더미 데이터로 테스트를 하는 경우 아래와 같이 redux에 더미데이터를 생성한다. (faker 라이브러리 사용)
// 서버에서 20개씩 데이터를 불러오는 경우 시뮬레이션
export const generateDummyPost = (number) => Array(number).fill().map(() => ({
id: shortId.generate(),
User: {
id: shortId.generate(),
nickname: faker.name.findName()
},
content: faker.lorem.paragraph(),
Images: [{
src: faker.image.image(),
}],
Comments: [{
User: {
id: shortId.generate(),
nickname: faker.name.findName()
},
content: faker.lorem.sentence(),
}],
}));
2. saga를 통해 action을 받아 작업을 처리해준다.
function* loadPost(action){
try{
// const result = yield call(loadPostAPI);
const id = shortid.generate();
yield delay(1000);
yield put({
type: LOAD_POST_SUCCESS,
data: generateDummyPost(10),
});
}
catch(err){
yield put({
type: LOAD_POST_FAILURE,
data: err.response.data,
})
}
}
function* watchLoadPost(){
yield takeLatest(LOAD_POST_REQUEST, loadPost);
}
export default function* postSaga(){
yield all([
fork(watchAddPost),
fork(watchLoadPost),
fork(watchRemovePost),
fork(watchAddComment),
]);
}
3. scrollY, clientHeight, scrollHeight 값을 통해 스크롤이 끝까지 갔을 때 자연스럽게 로딩해주는 것을 구현
(에러 발생)
const Home = () => {
const { me } = useSelector(state => state.user);
const { mainPosts, hasMorePost } = useSelector(state => state.post);
const dispatch = useDispatch();
useEffect(() =>{
dispatch({
type: LOAD_POST_REQUEST,
})
},[]);
useEffect(() => {
function onScroll(){
// console.log(window.scrollY, document.documentElement.clientHeight, document.documentElement.scrollHeight);
// 마지막 scrollY + clientHeight = scrollHeight
// 스크롤 다 내리면 새로운 게시글 로딩
if (window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight -300){
if(hasMorePost){
dispatch({
type: LOAD_POST_REQUEST,
})
}
}
}
window.addEventListener('scroll', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
};
}, [hasMorePost]);
시행 착오 1)
스크롤이 마지막 도달하기 300px이전에 미리 로딩해서 게시글을 불러오기 위해 조건문을 equal에서 등호로 바꿔보았다.
window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight -300
하지만 scroll이 미세하게 측정되기 때문에 요청이 1초에 수십 개가 발생한다.
시행 착오 2)
그래서 redux-saga effect 중 throttle을 통해 5초에 한번의 요청만 받게 설정해보았다.
function* watchLoadPost(){
yield throttle(5000, LOAD_POST_REQUEST, loadPost);
}
그러나 5초에 한번만 할 뿐 기존 요청을 취소 시켜주지 않기 때문에 5초 뒤에 또 요청이 성공하게 된다.

시행 착오3)
요청을 1번만 보낼 수 있는 방법이 없을까?
reducer에서 immer를 사용하여 설정해놓은 상태를 사용하면 된다.
case LOAD_POST_REQUEST:
draft.loadPostLoading = true;
draft.loadPostDone = false;
draft.loadPostError = null;
break;
case LOAD_POST_SUCCESS:
draft.loadPostLoading = false;
draft.loadPostDone = true;
draft.mainPosts = action.data.concat(draft.mainPosts);
draft.hasMorePost = draft.mainPosts.length < 50; // 게시글이 50개 이상이면 false (50개씩만 불러오기)
break;
case LOAD_POST_FAILURE:
draft.loadPostLoading = false;
draft.loadPostError = action.error;
break;
컴포넌트 useEffect 두 번째 인자에 loadPostLoading을 추가하고 if문에 hasMorePost && !loadPostLoading라고 수정하여 loading이 false일때만 요청이 가게끔 설정했다.
const { me } = useSelector(state => state.user);
const { mainPosts, hasMorePost, loadPostLoading } = useSelector(state => state.post);
const dispatch = useDispatch();
useEffect(() =>{
dispatch({
type: LOAD_POST_REQUEST,
})
},[]);
useEffect(() => {
function onScroll(){
// console.log(window.scrollY, document.documentElement.clientHeight, document.documentElement.scrollHeight);
// 마지막 scrollY + clientHeight = scrollHeight
// 스크롤 다 내리면 새로운 게시글 로딩
if (window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight -300){
if(hasMorePost && !loadPostLoading){
dispatch({
type: LOAD_POST_REQUEST,
})
}
}
}
window.addEventListener('scroll', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
};
} , [hasMorePost, loadPostLoading]);

+ 추가로
그리고 throttle은 다시 takeLatest로 수정해주었다
(요청이 2번 가는 경우도 있는데 이게 최종본이 아니라 나중에 lastId방식을 적용하면서 한번 더 디벨롭할 예정)
React-virtualized
infite scrolling을 하면 유저가 계속해서 게시글을 수 천개, 수 만개 이렇게 불러오면 브라우저 메모리가 터져버릴 수도 있다.
(컴퓨터는 메모리가 커서 괜찮은데 모바일의 경우 메모리가 작아서 터질 수 있음)
이럴 때 사용하는 것이 react-virtualized이다. (인스타에서 사용)
원리
수천개의 로딩된 게시글 중 보여지는 한 번에 보여지는 게시글 3~4개만 갖고있고 나머지는 메모리에 갖고있는 방식이다.

인스타를 보면 아무리 스크롤을 해도 최대 8개만 불러와지는 것을 볼 수 있다.
참고
인프런 강의 - 노드버드
'Dot Programming > React ∙ Next.js' 카테고리의 다른 글
[Next.js] Front 서버 배포시 First Load JS 용량 줄이기 (0) | 2021.02.13 |
---|---|
[React] 불변성 관리 라이브러리 Immer사용하기 (0) | 2021.01.22 |
[React] 더미데이터를 만들 때 필요한 Shortid, faker 라이브러리 (0) | 2021.01.20 |
[React/ Next.js] Redux-saga 설치 및 알아보기 (vs thunk / generator, effect) (0) | 2021.01.16 |
AWS로 Next.js 배포하기3 (pm2) (0) | 2021.01.14 |