[오즈의 제작소 NINJA] 리덕스 환경에서 댓글 무한 로딩 적용하기 (2)

17852 단어 projectproject

3-(3) 무한스크롤을 위한 프론트엔드 변화

🙏 방식

이 컴포넌트를 기준으로 threshold값 넘어가면 다음 댓글들을 로드함.

   {!isDisable && (
        <div className={'loading'} ref={target}>
          {loading}
        </div>
      )}

isDisablefalse 이면 DOM 상에 노출된다. => 가시성 관찰 (가져올 댓글 o)
isDisabletrue 이면 DOM 상에서 지운다. => 가시성 관찰 중단 (가져올 댓글 x)

1. create observer & onIntersect handler

  const onIntersect = (entries, observer) => {
    if (entries[0].isIntersecting) {
      dispatch(
        loadAllComments({ id: postData.id, page: page, comments: comments }),
      );
      observer.unobserve(target.current); //target.current가 변경 되면
    }
  };

entries[0].isIntersecting 은 교차되었는지에 대한 boolean 값을 갖는다. 교차되었다면,
loadAllComments 액션을 수행한다. loadAllComments 이지만 page값 부터 10개의 댓글을 가져오도록 변경하였다.
observer.unobserver(target.current) 는 현재 타겟에 대한 감시를 중단한다.

  useEffect(() => {
    if (page % 10 === 0 && page !== 0) {
      const observer = new IntersectionObserver(
        (entries, observer) => onIntersect(entries, observer),
        { threshold: 1 },
      );
      // 갱신 해주지 않으면 이전의 page에 접근해서 요청함.
      if (target.current) {
        observer.observe(target.current);
      }
      return () => observer && observer.disconnect();
    } else {
      setIsDisable(true);
    }
  }, [page]);

page값은 댓글의 길이 값을 갖고있다. ( 지금 댓글의 길이 다음 댓글 부터 가져와야하므로 )

만약 댓글의 길이(=page) 를 10으로 나눈 나머지 값이 0이 아니라면 모든 댓글을 다 가져온 것이므로 setIsDisable(true)을 통해 감시하던 객체를 없앤다.

10으로 나눈 나머지 값이 0이라면 아직 가져올 댓글이 있거나, 마지막 댓글인 것이므로 감시를 계속한다. onIntersect() 에서 갱신된 page값을 참조할 수 있도록 새로운 observer를 만든다.

❓ page값이 갱신될 때 마다 옵저버를 새롭게 new 해주면 계속 옵저버들이 쌓이는 걸까? 참조가 끊겨 가비지가 될 수 있음. => 확인 요망 ❗️

2. update page state

  useEffect(() => {
    setPage(comments.length);
  }, [comments.length]);

comments.length 값을 의존성 배열안에 넣어준다. 값이 변하면 새로운 댓글들을 로드한 것이므로 setPage(comments.length) 해주어 값을 갱신해준다.

3. redux loadAllCommentsSaga

export const loadAllComments = createAction(LOAD_ALL_COMMENTS);
function* loadAllCommentsSaga(action) {
  yield put(startLoading(LOAD_ALL_COMMENTS));
  try {
    const loadAllComments = yield call(
      commentsAPI.getCommentsByPostId,
      action.payload,
    );

    yield put({
      type: LOAD_ALL_COMMENTS_SUCCESS,
      payload: [
        ...action.payload.comments,
        ...loadAllComments.data.allComments,
      ],
    });
  } catch (e) {
    yield put({
      type: LOAD_ALL_COMMENTS_FAILURE,
      payload: e,
      error: true,
    });
  }
  yield put(finishLoading(LOAD_ALL_COMMENTS));
}
yield put({
      type: LOAD_ALL_COMMENTS_SUCCESS,
      payload: [
        ...action.payload.comments,
        ...loadAllComments.data.allComments,
      ],
    });

기존의 댓글 배열에 가져온 댓글 배열을 이어서 comment reducer에게 전달.

[LOAD_ALL_COMMENTS_SUCCESS]: (state, { payload }) => {
      return {
        ...state,
        comments: payload.map((comment) => ({
          ...comment,
          Comments: comment.Comments.map((reply) => ({
            ...reply,
            isEditClicked: false,
            isMenuClicked: false,
          })),
          isEditClicked: false,
          isMenuClicked: false,
          isClicked: false,
        })),

        error: null,
      };

immer를 사용하지않아 더러운 모습이다......
action.payload 로 전달된 댓글 배열들을 리덕스 상태에 주입한다.

4. 느낀점

  • onIntersect에서 state를 사용하는 경우 state값이 갱신될 때 마다 onIntersect 함수를 다시로드 해주어야함. 이렇게 안하면 과거 page값을 참조.

5. Reference

좋은 웹페이지 즐겨찾기