-
[React] Intersection Observer API : 무한 스크롤 구현React 2022. 7. 3. 17:25
Intersection Observer API - Web API | MDN
Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document 의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법입니다.Intersection Observer API는 타겟 요소와 상위 요소 또는
developer.mozilla.org
🤔Intersection Observer API란?
Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document 의 viewport사이의 교차점 (intersection) 내의 변화를 비동기적으로 관찰하는 방법이다.
즉, 내가 지정한 요소가 화면(뷰포트) 상에 보이고 있는지를 관찰하는 API이다.
왜 사용할까?
기존의 scroll 같은 이벤트 기반의 요소 관찰에서 발생하는 렌더링 성능이나 이벤트 연속 호출 같은 문제 없이 사용 가능
- scroll 이벤트는 동기적으로 실행 -> 메인 스레드에 큰 부하를 줄 수 있다.
- getBoundingClientRect 사용시 reflow 발생
reflow : 엘리먼트가 문서내에서 어디에 위치하는지에 대한 정보를 계산해서 다시 렌더링을 하는 현상
Intersection Observer API는 비동기적으로 실행하여 메인 스레드에 영향을 주지 않는다.
IntersectionObserverEntry의 속성을 활용하면, reflow 없이 element의 위치를 감지할 수 있다.
어떤 상황에 사용할까?
- Lazy-loading
- infinite scrolling
- 광고 수익 계산을 위한 광고의 가시성 확인
- 사용자가 결과를 봤는지에 따라 애니메이션 혹은 어떠한 동작을 수행할지 안할지 결정
어떻게 사용할까?
intersection observer 생성
const io = new IntersectionObserver(callback, options); io.observe(element);
new IntersectionObserver()를 통해 생성한 인스턴스(io)로 관찰자(Observer)를 초기화하고 관찰 대상(element) 지정
Methods
io.observe(element) // => element 관찰 시작 io.unobserve(element) // => element 관찰 중단 io.disconnect() // => 모든 element 관찰 중단
callback
관찰 대상이 등록되거나 가시성(Visibility, 보이는지 보이지 않는지) 변화가 생기면 콜백 실행
콜백은 2개의 인수(entries, observer)를 가진다.
entries
const io = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { entry.boundingClientRect // => element.getBoundingClientReac() entry.intersectionRect // => element의 감지된 부분의 사각형 정보 entry.intersectionRatio // => element가 루트 요소와 얼마나 겹치는 지 0~1 사이 entry.isIntersecting // => element의 교차 상태 entry.rootBounds // => 루트 요소의 사각형 정보 entry.target // => element entry.time // => element와 루트 요소의 교차 발생시간 }) }, options) io.observe(element)
observer
콜백이 실행되는 해당 인스턴스를 참조
options
root => 기본값 null, 관찰 대상을 검사하기 위해 뷰포트 대신 사용할 요소 지정
rootMargin => 기본값 0px 0px 0px 0px, root의 margin 값 설정
threshold => 기본값 0, 콜백이 실행되기 위한 백분율 지정 0 ~ 1, 혹은 배열
✨리액트로 무한 스크롤 구현
const [list, setList] = useState([]); const [target, setTarget] = useState(null); const [pageNumber, setPageNumber] = useState(0); // 초기값 렌더링, pageNumber 바뀔 때마다 호출 useEffect(() => { // ~데이터 가져오는 로직~ // setList((list) => list.concat(data)) }, [pageNumber]); const callback = useCallback( (entry, observer) => { if (entry[0].isIntersecting) { setPageNumber((prev) => prev = prev + 1) observer.unobserve(entry[0].target); } }, [setPageNumber] ); useEffect(() => { if (!target) return; const io = new IntersectionObserver(callback); io.observe(target); return () => observer.disconnect(); }, [callback, target]); return ( <> <div> {list.map((item) => ( // list item 요소 출력 ))} </div> <div ref={setTarget}></div> </> );
clean-up
- 스크롤을 내려 새로운 목록을 받아오게 되므로 관찰할 대상이 바뀌기 때문에 disconnect()로 관찰요소를 없애고 새로 지정
useCallback
- 재렌더링 시 함수가 새로 만들어지지 않는다. 함수 안에서 사용하는 state 또는 props가 있다면 deps 배열에 포함
custom hook으로 만들기
// useIntersectionObserver.jsx import { useEffect, useState } from "react"; const useIntersectionObserver = ({ callback }) => { const [target, setTarget] = useState(null); useEffect(() => { if (!target) return; const observer = new IntersectionObserver(callback); observer.observe(target); return () => observer.unobserve(target); }, [onIntersect, target]); return { setTarget }; }; export default useIntersectionObserver;
// 사용할 곳 const callback = useCallback((entry, observer) => {생략}, []) const { setTarget } = useIntersectionObserver({ callback }); return ( <> // 생략 <div ref={setTarget}></div> </> );
✍️개인적인 정리
+ 렌더링이 두 번 발생
구현하다가 처음에 데이터 호출이 두 번씩 돼서 왜 그런가 했더니 React.StrictMode가 적용되어서 그랬다.
creat-react-app으로 생성하면 잠재적인 문제를 알아내기 위한 도구로 StrictMode가 기본으로 적용된다.
개발 모드에서만 활성화되기 때문에, 프로덕션 빌드에는 영향을 끼치지 않는다고 한다. 지워주면 한 번만 호출이 된다.
ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById("root") );
반응형'React' 카테고리의 다른 글
[React] 코드 분할과 React.lazy (0) 2022.08.11 [React] React에서 setInterval 사용 (0) 2022.07.22 [React] 제어/비제어 컴포넌트 (0) 2022.07.10 [React] 조건부 렌더링 시 "falsy" 값 렌더링 주의 (0) 2022.06.28 [React] TypeError: Array.map is not a function (0) 2022.06.14