Intersection Observer 소스 코드 추가했습니다.

https://github.com/ZeroCho/react-nodebird/tree/master/intersection

문의하시는 분들(인피니트 스크롤링에서 height 소수점 발생 문제나 쓰로틀링 적용 안 됨 등의 문제가 발생하시는 분)이 많아서 인터섹션옵저버 적용한 소스코드 공유합니다.

훨씬 더 인피니트 스크롤링을 간단하게 적용하실 수 있습니다. 간단히 설명드리자면,

먼저 react-intersection-observer 패키지를 설치하고(react에서는 dom에 접근하려면 ref를 사용해야하는데 패키지를 안 쓰고도 할 수 있으나  조금 귀찮습니다)

다음 코드를 window.addEventListener('scroll')이 있는 useEffect 대신 넣으시면 됩니다.

const [ref, inView] = useInView();

useEffect(
  () => {
    if (inView && hasMorePosts && !loadPostsLoading) {
      const lastId = mainPosts[mainPosts.length - 1]?.id;
      dispatch({
        type: LOAD_POSTS_REQUEST,
        lastId,
      });
    }
  },
  [inView, hasMorePosts, loadPostsLoading, mainPosts, id],
);

JSX단에서는 다음과 같이 넣어주세요.

{mainPosts.map((post) => <PostCard key={post.id} post={post} />)}
<div ref={hasMorePosts && !loadPostsLoading ? ref : undefined} />

PostCard들 아래에 위치한 div가 화면에 보이는 순간(즉, 마지막 게시글을 봤다는 소리겠죠) inView가  true가 되면서 useEffect가 작동합니다. inView가 true고, 게시글이 더 있고, 로딩중이 아니라면 새 게시글을 가져오는 액션이 dispatch 됩니다.

ref props를 보시면 hasMorePosts && !loadPostsLoading ? ref : undefined로 되어있는데 게시글이 더 있고, 로딩중이 아닐 때에만 ref가 연결되기 때문에 이 상황이 아니면 intersectionObserver가 해제됩니다.

현재는 가장 마지막 게시글보다 더 아래로 내려야만 인피니트 스크롤링이 작동하지만, 만약 아래에서 두번째, 아래에서 세 번째 등의 기준을 두고 싶으시다면 mainPosts.map 사이에 div를 끼어넣거나, 특정 인덱스의 PostCard의 ref로 인터섹션옵저버의 ref를 전달하시면 됩니다.

Namlulu 프로필
Namlulu 2021.08.02 저 방식으로 하면 자꾸 2번씩 요청이 되어서 그냥 직접 구현했습니다. import { useEffect, useRef } from 'react'; import { useSelector, useDispatch } from 'react-redux'; // import AppLayout from '../components/AppLayout'; import PostForm from '../components/PostForm'; import PostCard from '../components/PostCard'; import { LOAD_POSTS_REQUEST } from '../reducers/post'; import { LOAD_MY_INFO_REQUEST } from '../reducers/user'; const Home = () => { const dispatch = useDispatch(); const { me } = useSelector((state) => state.user); const { mainPosts, hasMorePosts, loadPostsLoading } = useSelector( (state) => state.post, ); const viewport = useRef(null); const target = useRef(null); useEffect(() => { dispatch({ type: LOAD_MY_INFO_REQUEST, }); }, []); useEffect(() => { const options = { root: viewport.current, threshold: 0, }; const handleIntersection = (entries) => { entries.forEach((entry) => { if (!entry.isIntersecting) { return; } if (hasMorePosts && !loadPostsLoading) { const lastId = mainPosts[mainPosts.length - 1]?.id; dispatch({ type: LOAD_POSTS_REQUEST, lastId, }); } }); }; const io = new IntersectionObserver(handleIntersection, options); if (target.current) { io.observe(target.current); } return () => io && io.disconnect(); }, [viewport, target, loadPostsLoading, hasMorePosts, mainPosts]); return ( <AppLayout ref={viewport}> {me && <PostForm />} {mainPosts.map((post) => ( <PostCard key={post.id} post={post} /> ))} <div ref={hasMorePosts && !loadPostsLoading ? target : undefined} /> </AppLayout> ); }; export default Home;
saltcoffee 프로필
saltcoffee 2021.11.12 윗댓글처럼 자꾸 2번 요청 되는거 때문에 테스트 하다가 알게된건 화면에 내용물 길이가 충분하지 않은경우 화면에 바로 마지막게시글이 보여서 inView가 true 되면서 useEffect의 if (inView && hasMorePosts && !loadPostsLoading) 이구문 통과 되서 2번 불러오는 현상이 있는거같은데 if (inView && hasMorePosts && !loadPostsLoading && document.documentElement.clientHeight < document.documentElement.scrollHeight) 이렇게 화면높이 < 스크롤높이 추가하면 마지막게시물은 화면높이 아래에 있을테니 일단은 첫 로딩시 두번 불러오진 않는듯
손서연 프로필
손서연 2022.05.02 useEffect 내부에서 콘솔로 inView를 찍어보니 값의 변화에 따라 업데이트가 되지 않아서 여러 시도를 해봤는데 방법이 다양한 것 같습니다. 그 중에서 제일 간편했던 코드는 함수에 리턴 값으로 inView를 다시 받고, 그 값에 따라 기존의 코드를 진행하니 크게 문제는 없던 것 같습니다. 윗분들의 코드는 제가 해보지 않았지만 혹시나 시도해보고 안되신다면 한번 제 방법으로 해보셔도 될 것 같습니다. 기존의 코드는 현영님이 올려주신 gitHub코드를 똑같이 하고, pages내부에 index.js 쪽 스크롤 마지막 부분에 대한 코드에 useEffect(()=>{ (inView)=>{ return inView; } (이 부분만 수정하면 됩니다) console.log(inView) if (inView && hasMorePost && !loadPostsLoading) { const lastId = mainPosts[mainPosts.length - 1]?.id; dispatch({ type: LOAD_POSTS_REQUEST, lastId, }); } },[inView, id, hasMorePost, loadPostsLoading, mainPosts])
채널톡 아이콘