SPA에서 자주 사용하는 Infinity Scroll을 IntersectionObserver로 구현한 과정입니다.
// testPage 컴포넌트
let startNum = 0;
const TestPage = () => {
1. const reduxData = useSelector((state) => state.data.stores);
const [count, setCount] = useState(0);
const [stores, setStores] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const fetchData = () => {
try {
2. let seperatedData = reduxData.slice(startNum, startNum + 3);
3. startNum += 3;
4. setStores((prev) => [...prev, ...seperatedData]);
} catch (e) {
console.log(e);
}
};
if (reduxData !== null) fetchData();
5. setLoading(false);
}, [count, reduxData]);
if (stores === []) {
return <div>로딩 중..</div>;
}
return (
<div>
<h1>테스트</h1>
<StoreBuild loading={loading} stores={stores} />
<FetchBuild loading={loading} setCount={setCount} />
</div>
);
};
1. 현재 Redux를 이용해 전체 데이터를 useSelector를 이용하여 불러옵니다.
2. 배열 메소드 slice를 이용하여 reduxData의 0번 ~ 3번 데이터를 나눴습니다.
3. startNum +3을 하면서 다음 추가 데이터 로드때 불러올 배열 설정,
4. 현재 화면에 표시할 데이터들을 기존 데이터에 추가될 데이터를 합치는 과정입니다.
5. 여기서 loading은 추가로 데이터를 불러올 때 지연될 시간을 고려한 것입니다.
const StoreBuild = ({ loading, stores }) => {
return (
<div className="allStores">
1. {loading && "로딩 중"}
{!loading &&
stores &&
stores.map((store) => (
<div className="item-card" key={store.id}>
<Link to={`/detail/${store.id}`}>
<div className="card-img"></div>
<div className="card-text">
<div className="card-name">{store.name}</div>
<div className="card-detail">
<AiFillStar />
<p>
{store.grade} ({store.feedNum}) · 배달비{" "}
{store.deliveryCost}원
</p>
</div>
</div>
</Link>
</div>
))}
</div>
);
};
const FetchBuild = ({ loading, setCount }) => {
1. const fetchMoreTrigger = useRef(null);
2. const fetchMoreObserver = new IntersectionObserver(([{ isIntersecting }]) => {
if (isIntersecting) setCount((prev) => prev + 1);
});
3. useEffect(() => {
fetchMoreObserver.observe(fetchMoreTrigger.current);
return () => {
fetchMoreObserver.unobserve(fetchMoreTrigger.current);
};
}, []);
return (
<div
id="fetchMore"
className={loading ? "loading" : ""}
1. ref={fetchMoreTrigger}
/>
);
};
1. 기본적으로 FetchBuild 컴포넌트는 StoreBuild 컴포넌트 밑에 위치하여 렌더링 할 <div> 요소는 페이지의 맨 아래에 위치하고 있습니다. 이 요소에 ref를 설정하여 DOM을 직접 접근했습니다.
2. isIntersecting을 이용하여 관찰하고자 하는 요소가 설정한 구역(여기선 default 구역)에 들어왔는지 여부를 확인할 수 있습니다.
isIntersecting이 true 일 때 count 값을 변화시켜 상위 컴포넌트에서 재렌더링, 데이터를 추가로 로드할 수 있도록 설정했습니다.
3. useEffect의 뒷정리 함수를 이용해 이벤트를 제거했습니다.