프리온보딩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()
},[])
}
React Hooks를 사용해 function component로 만들자고 결정했는데,
Hooks 숙련도가 부족하여 같은 Warning을 계속 마주하게 됩니다.
React Hook useEffect has a missing dependency
알아보니, useEffect
의 두 번째 인자 deps
에 관한 것이었습니다.
어떤 state
값을 useEffect
의 콜백에서 사용하는데, deps
에 해당 state가 없다면,
warning이 발생합니다.
useEffect
를 componentDidMount
처럼 쓰고 싶은데, state를 배열 안에 넣으면, 그렇게 동작하지 않기 때문입니다.setComments
를 다음과 같이 수정하여 Warning을 해결합니다. setComments(comments => comments.concat(loadedComments));
단순 실수였지만, 가장 많은 시간을 허비합니다..
이제, 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>
)
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])
deps
에 page
를 추가합니다.
굳이, page
를 set하고 loadComments
할 필요 없이
page
만 set 하면, useEffect에 의해 loadComments
가 실행됩니다.
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
합니다.
스크롤 이벤트 내에서, 스크롤이 페이지 끝에 도달했음을 알아야합니다.
요소 사이즈와 스크롤 구하기
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하여 로드합니다.
스크롤 하여, 다음 페이지를 반복적으로 불러옵니다.