무한 스크롤로 목록 구현

Yuno·2021년 7월 27일
2

계기

프리온보딩FE 1차 기업 과제 제출을 위해, 무한 스크롤로 Comment 목록을 만들게 되었습니다.

시작

처음으로 페어프로그래밍을 해보았습니다.
짝 분과 함께 만나서 진행하기로 하여 수월하게 시작하게 되었습니다.

페이지 끝에 도달하면 데이터를 로딩할 리스트 컴포넌트를 만들었습니다.

import CommentList from './components/CommnetList';
import './App.css';

function App() {
 
  return (
    <div className="App">
      <CommentList />
    </div>
  );
}

export default App;
function CommentList() {
  const [comments, setComments] = useState([]);

  const loadComments = async ()=> {
    const requestURL = 
          `https://jsonplaceholder.typicode.com/comments?_page=1&_limit=10`;

    try {
      const {data : loadedComments} = await axios.get(requestURL);

      setComments(comments.concat(loadedComments));
    }
    catch(err) {
      console.error(err)
    }
  }

  useEffect(()=>{
    loadComments()
  },[])
}

위기1 : Warning

React Hooks를 사용해 function component로 만들자고 결정했는데,
Hooks 숙련도가 부족하여 같은 Warning을 계속 마주하게 됩니다.

React Hook useEffect has a missing dependency

알아보니, useEffect의 두 번째 인자 deps에 관한 것이었습니다.

어떤 state 값을 useEffect의 콜백에서 사용하는데, deps에 해당 state가 없다면,
warning이 발생합니다.

해결 방법

  • 해당 state를 배열 안에 넣어줍니다.
    처음엔, 이 방법이 이해되지 않았습니다.
    useEffectcomponentDidMount처럼 쓰고 싶은데, state를 배열 안에 넣으면, 그렇게 동작하지 않기 때문입니다.

  • 함수 형 업데이트를 사용합니다
    hooks의 set함수는 class component의 useState처럼 함수 형 업데이트가 가능합니다.
    setComments를 다음과 같이 수정하여 Warning을 해결합니다.
setComments(comments => comments.concat(loadedComments));

위기 2 : 렌더링

단순 실수였지만, 가장 많은 시간을 허비합니다..

이제, loadComments로 Comments는 준비 됐고,
리스트로 렌더링 하기 위한 renderComments 함수를 만듭니다.

  const renderComments = ()=> {
        return comments.map(comment=> 
            <Comment 
                key={comment.id} 
                id={comment.id} 
                email={comment.email}
                body={comment.body}
            />
        )
    }

    return (
        <ul className='commnet-list'>
            {renderComments}
        </ul>
    )

이렇게 저렇게 테스트 해봐도 계속 오류가 뜨면서 렌더링 되지 않게됩니다.

1시간 가량 고치고 확인하고를 반복하고 나서 renderComments를 호출한게 아니라,
함수 이름만 쓴 것을 발견...

당연하게도, 함수를 호출해야만 React Component List가 반환됩니다.

 return (
   <ul className='commnet-list'>
     {renderComments()}
   </ul>
 )

무한 스크롤 구현

loadCommnets함수 page인자 추가

const [comments, setComments] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [page , setPage] = useState(1);

const loadComments = async page=> {
  const requestURL = 
        `https://jsonplaceholder.typicode.com/comments?_page=1&_limit=10`;
  
  setIsLoading(true);
  
  try {
    const {data : loadedComments} = await axios.get(requestURL);

    setComments(comments.concat(loadedComments));
    setIsLoading(false);
  }
  catch(err) {
    console.error(err)
  }
}

useEffect(()=>{
  loadComments(page)
},[page])

depspage를 추가합니다.

굳이, page를 set하고 loadComments 할 필요 없이
page만 set 하면, useEffect에 의해 loadComments가 실행됩니다.

scroll Event 추가

window에 핸들러를 추가합니다.

function AnyComp() {
  window.addEventListener('click',onClick);
}

이렇게 하면 컴포넌트가 렌더링 될 때마다 매번 핸들러가 추가되기 때문에, 적절하지 않습니다.

마운트 될 때, 한번만 추가 되도록 useEffect를 사용합니다.

useEffect(()=>{
  window.addEventListener('scroll', onScroll, {passive:true});

  return () => window.removeEventListener('scroll', onScroll, {passive:true});
},[]);

UnMount 될 때, 리스너를 해제하도록 return에서 remove 합니다.

scroll 정도 구하기

스크롤 이벤트 내에서, 스크롤이 페이지 끝에 도달했음을 알아야합니다.

요소 사이즈와 스크롤 구하기

const onScroll = ()=> {
  const {scrollHeight,clientHeight,scrollTop} = document.documentElement;
  const degree = scrollTop / (scrollHeight-clientHeight);

  if (degree >= 1) {
    setIsViewEnd(true);
  }
}

document.documentElement로 문서 요소에 접근합니다.
문서 전체 높이인 scrollHeight,
화면에 보이는 높이 clientHeight,
스크롤 한 높이 scrollTop을 활용하여, 문서 내에서 얼마나 스크롤 했는지 계산합니다.

degree 변수가 1이 되면 페이지 끝에 도달한 것 입니다.

끝에 도달하면, setIsViewEnd 값을 true로 set합니다.

useEffect(()=> {
  if (isViewEnd && !isLoading) {
    setPage(page=> page+1);
    setIsViewEnd(false);
  }
},[isViewEnd,isLoading])

스크롤이 페이지 끝에 도달하고, 현재 로딩중이 아니라면 다음 페이지를 set하여 로드합니다.

스크롤 하여, 다음 페이지를 반복적으로 불러옵니다.

느낀점

  • 겪은 실수를 반복하지 말자
  • 리액트 숙련도를 향상시키는 계기가 되었다
  • git commit message guide를 지키려고 단계별로 개발하여, 예전보다 체계적이었다
profile
web frontend developer

0개의 댓글