Skip to content

feat: #88/대시보드 상세 페이지 할 일 카드 무한 스크롤#101

Merged
jihye5081 merged 1 commit intomainfrom
feat/#88/task-pagenation
Mar 29, 2025
Merged

feat: #88/대시보드 상세 페이지 할 일 카드 무한 스크롤#101
jihye5081 merged 1 commit intomainfrom
feat/#88/task-pagenation

Conversation

@hyeonjiroh
Copy link
Owner

#️⃣ Issue Number

#88


📝 요약

  • Column 컴포넌트에서 IntersectionObserver를 활용하여 TaskCard 리스트를 무한 스크롤 방식으로 불러오도록 구현

🛠️ PR 유형

  • 새로운 기능 추가
  • 버그 수정
  • CSS 등 사용자 UI 디자인 변경
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 변수명 변경 등)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📚 참고 자료

무한 스크롤 흐름

  1. 마운트
  2. handleLoad 함수를 호출하여 첫 페이지 카드들 불러옴
  3. 마지막 카드 요소에 ref 연결
  4. ref 요소, 즉 마지막 카드가 화면에 보이면 IntersectionObserver 감지
  5. handleLoad 함수를 다시 호출하여 다음 페이지 카드들 불러옴
  6. 더 이상 받아올 데이터가 없으면 isLasttrue가 되고 데이터 불러오기 완료

isLoadingisLast state의 필요성

isLoading
데이터 요청 중 중복 호출 방지
isLast
마지막 페이지 여부를 확인하여 무한 요청 방지

handleLoad 함수

  • 중복 요청과 마지막 페이지 요청을 방지한다.
    if (isLoading || isLast) return;
    
  • 받아온 카드를 누적으로 저장한다.
    setItems(prev => [...prev, ...newCards]);
    
  • 마지막 페이지를 판단하여 isLast 값을 갱신한다.(페이지 사이즈보다 데이터의 길이가 작거나 nextCursorId가 null이면 마지막 페이지라는 의미)
     if (newCards.length < PAGE_SIZE || nextCursorId === null) {
       setIsLast(true);
     }
    

마지막 카드에만 ref 걸기

{items.map((item, index) => (
  <div
    key={item.id}
    ref={index === items.length - 1 ? observerRef : null}
  >
    <TaskCard {...item} columnTitle={title} />
  </div>
))}
  • TaskCard 리스트 중 마지막 TaskCard div에만 ref를 달아준다.
  • 그럼 해당 ref를 통해 IntersectionObserver가 마지막 카드가 보이는 것을 감지한다.

IntersectionObserver 등록

  • 마지막 페이지면 observer 등록을 하지 않는다.
    if (isLast) return;
    
  • IntersectionObserver는 DOM 요소가 뷰포트에 들어오는 것을 감지한다.
    • 관찰 중인 요소(entries[0]이 뷰포트에 보이면 handleLoad 함수 실행
      if (entries[0].isIntersecting) {
            handleLoad();
          }
      
  • observerRef.current(ref로 연결된 DOM 요소, 즉 마지막 TaskCard div)를 감시하기 위해 observer에 연결
    const current = observerRef.current;
    if (current) observer.observe(current);
    
  • 리스트가 새로 업데이트되면 ref가 다른 요소에 재할당되는데, 이전 ref에 연결된 observer를 끊지 않으면 여러 요소가 동시에 감시되기 때문에, 현재 감시 중인 요소(current, 즉 마지막 TaskCard div)를 그만 감시하도록 해준다.
    if (current) observer.unobserve(current);
    
  • 모든 관찰 중인 요소를 제거하고 해당 observer 인스턴스를 메모리에서 정리해준다.
    observer.disconnect();
    

@hyeonjiroh hyeonjiroh self-assigned this Mar 29, 2025
@vercel
Copy link

vercel bot commented Mar 29, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
taskify ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 29, 2025 8:59am

//로컬 이미지 테스트 하기 위해 localhost를 넣었습니다.
domains: ["sprint-fe-project.s3.ap-northeast-2.amazonaws.com", "localhost"],
},
reactStrictMode: false,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로컬에서 작업하시면서 strict껐으면 git으로는 추적 안하는게 좋을것 같아요

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안 그래도 이 부분 지우는 걸 잊고 있었는데, 우선 다음 작업까지 하고 다시 true로 해놓겠습니다!

Comment on lines +53 to +72
useEffect(() => {
if (isLast) return;

const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
handleLoad();
}
},
{ threshold: 0.5 }
);

const current = observerRef.current;
if (current) observer.observe(current);

return () => {
if (current) observer.unobserve(current);
observer.disconnect();
};
}, [cursorId, isLoading, isLast]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

observer하는거는 재사용의 목적 + 이 페이지에서는 비즈니스로직일 필요가 없어보여서 추상화해보면 좋겠네요.
useIntersection같은 커스텀훅으로요

Copy link
Owner Author

@hyeonjiroh hyeonjiroh Mar 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 곳에서도 무한 스크롤을 사용해서 추상화해보면 좋겠네요. 의견 감사합니다!

Comment on lines +14 to +17
let query = `size=${size}&columnId=${columnId}`;
if (cursorId !== null) {
query += `&cursorId=${cursorId}`;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 다른분들 생산성을 위해서 query로 붙여주는 함수로 한번 분리해보는것도 좋겠네요.
저는 exceptEmptyValue라는 함수를 만들어서 값이 없는 인자는 쿼리를 안붙이도록 만들어서 쓰고 있어요.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다 고려해보고 해보도록 하겠습니다!

@jihye5081 jihye5081 merged commit a8b39dc into main Mar 29, 2025
3 checks passed
@jihye5081 jihye5081 deleted the feat/#88/task-pagenation branch March 29, 2025 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants