[React 15] Intersection Observer API를 활용한 무한스크롤 구현

뿌이·2024년 8월 28일
0

react 15버전

목록 보기
17/17

구조

일단 컴포넌트는 어떤 식의 구조로 되어있냐면
최상위 부모 : Cert.js
부모 : CompleteCert.js
자식 : CompleteItem.js

원리

react 17, 바닐라js는 정보가 많이 나와있는데 react15는 정보가 많이 없어서 적는다.
CompleteCert.js에서 CompleteItem 컴포넌트를 map 돌릴 때에
이미 페이지 1개당 50개의 데이터를 받는다.

그리고 #observer 에 닿으면 Intersection Observer API에 의해 감시당하면서
scroll 위치를 저장하고 추가적인 데이터를 불러오기위한 API가 호출된다.

이때 기존 값과 새로 받은 값을 더해서 completeCerts에 저장한다.
해당 API가 호출되어 redux에서 관리하고있던 completeCerts가 이전 값과 달라지면 바뀐 스크롤 값을 저장하여 스크롤을 다시 이전 위치(마지막으로 보고있었던 사용자의 스크롤 위치)로 돌려준다.

Cert.js

getCompleteList = (page_no, scrolled) => {
	let oldCompleteCerts = [];
        if(isScrolled){ //스크롤을 맨 밑에 닿은 경우
            oldCompleteCerts = this.props.completeCerts;
        }
  
  this.handleCheckedCancelSuccessCompletedList(certListByComplete, oldCompleteCerts);
}

handleCheckedCancelSuccessCompletedList = (list, oldList) => {
        const { setCerts, completeCount } = this.props;
        let oldCerts = _.defaultTo(oldList,[]);
        let newList = [];

        if(oldCerts.length > 0){
            newList = [...oldCerts , ...newList];
        }

        setCerts({
            completeCerts: newList,
            completeCount : completeCount,
        });
    }

render() {
	return (<div className='mmt_main_wrap'>
            <CompleteCert
            	getCompleteList={this.getCompleteList}
            />
			</div>)
}

CompleteCert.js

 constructor(props) {
        super(props);

        this.state = {
            pageNo: 1,
            scrollPosition: 0
        };
    }

    componentDidMount() {
        this.observer = new IntersectionObserver(this.handleObserver, {
            threshold: 0,
        });

        const observerTarget = document.getElementById("observer");
        if (observerTarget) {
            this.observer.observe(observerTarget);
        }
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        const {scrollPosition} = this.state;

        if (prevProps.completeCerts !== this.props.completeCerts && scrollPosition !== 0) {
            const scrollable = document.querySelector('.mmt_main_wrap');
            scrollable.scrollTop = scrollPosition;
        }
    }

    componentWillUnmount() {
        const observerTarget = document.getElementById("observer");
        if (observerTarget && this.observer) {
            this.observer.unobserve(observerTarget);
        }
    }

    // Intersection Observer 설정
    /**
     handleObserver: 교차점이 발생했을 때 실행되는 콜백 함수.
     entries: 교차점 정보를 담는 배열
     isIntersecting: 교차점(intersection)이 발생한 요소의 상태
     교차점이 발생하면 page 1 증가
     */
    handleObserver = (entries) => {
        const target = entries[0];
        const observer = document.querySelector("#observer");

        if (target.isIntersecting && this.props.progressCount === 0) {
            let totalPages = this.props.completeCount / 50 + 1;
            let plusedPageNo = this.state.pageNo + 1;

            if (plusedPageNo <= totalPages) {
                this.setState({pageNo: plusedPageNo, scrollPosition: observer.offsetTop}, () => {
                    this.props.getCompleteList(plusedPageNo, true);
                });
            }
        }
    };

render(){
	return (
    	 <div className="list_body">{completeCerts ? completeCerts.map((it, index) =>
                    <CompleteItem {...it}
                                   history={history}
                                   nahagoHRTno={nahagoHRTno}
                                   key={index}/>) : null}
                </div>
                <p id="observer"/>  
    )
}

참고

바닐라 js 예제
리액트 17 예제

profile
기록이 쌓이면 지식이 된다.

0개의 댓글