팀원분이 무한 스크롤을 구현하셨는데, 사용법을 정리해보려고 한다.
예제
const options = { threshold: 1.0 };
const callback = (entries, observer) => {
    entries.forEach((entry) => {
        if (entry.isIntersecting) {
            observer.unobserve(entry.target);
            console.log('화면에서 노출됨');
        } else {
            console.log('화면에서 제외됨');
        }
    });
}
const observer = new IntersectionObserver(callback, options);
observer.observe(
    document.getElementById('id')
);
React에서 활용한 코드를 살펴보자
useIntersectionObserver.js
import { useRef } from 'react';
const useIntersectionObserver = (callback) => {
  const observer = useRef(
    new IntersectionObserver(
      (entries, observer) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            callback();
            console.log('도달하였습니다');
          }
        });
      },
      { threshold: 0.5 }
    )
  );
  const observe = (element) => {
    observer.current.observe(element);
  };
  const unobserve = (element) => {
    observer.current.unobserve(element);
  };
  return [observe, unobserve];
};
export default useIntersectionObserver;
Custom Hook으로 어디서나 사용할 수 있게 구현되어 있다.
useRef를 사용하여 상위 컴포넌트 생애주기 동안 유지되는 값으로 사용할 수 있게 생성한다.
나머지는 예제와 유사하다.
threshold는 target element가 어느정도 보여지는지(덮어지는지)에 따라 동작이 실행된다.
ShowBlogList.jsx
const [page, setPage] = useState(1);
const target = useRef(null);
<div ref={target} style={{ width: '100%', height: 100 }} />
먼저 끝에 다다랐을 때 새 데이터를 가져오기 위해 리스트 아래에 ref로 지정을 해준다.
const [observe, unobserve] = useIntersectionObserver(() => {
    setPage((page) => page + 1);
  });
새로운 데이터를 가져오기 위해 페이지의 상태를 +1 되도록 수행해준다.
  useEffect(() => {
    if (isLoading) {
      if (target == null) {
        unobserve(target.current);
      }
    } else {
      observe(target.current);
    }
  }, [isLoading]);
target element가 없을때에는 교차방지를 위해 제거해준다.
target element를 관찰할 수 있도록 로딩이 아닐 때에 등록해준다.
useEffect(() => {
    const N = data?.documents.length;
    const totalCount = data?.meta.total_count;
    if (0 === N || totalCount <= N) {
      unobserve(target.current);
    }
  }, [blogList]);
데이터가 불러와지지 않았을 때와 서버에 모든 데이터(불러올 수 있는 모든 개수)를 모두 불러왔을 때 교차 방지를 위해 unobeserve해준다.
const updateData = async () => {
    if (page !== 1) {
      const response = await getBlogLists(title, page);
      const blogData = response.documents;
      setBlogList((prev) => [...prev, ...blogData]);
    }
  };
useEffect(() => {
    updateData();
  }, [page]);
페이지가 1이 아닐 때, 데이터를 추가해준다!
MainMap.jsx
export const makeNewMap = () => {
  const container = document.getElementById('map');
  const options = {
    center: new kakao.maps.LatLng(33.3577838, 126.4624306),
    level: 9
  };
  const map = new kakao.maps.Map(container, options);
  return map;
};
다른 컴포넌트에서 지도를 사용할 수 있도록 export로 코드를 빼주었다.
PlaceList.jsx
const listOnclickHandler = (item) => {
    dispatch(setDetailModalData(item));
    dispatch(setDetailModalOn(true));
    const map = makeNewMap();
    const geocoder = new kakao.maps.services.Geocoder();
    const callback = (result, status) => {
      if (status === kakao.maps.services.Status.OK) {
        const coords = new kakao.maps.LatLng(result[0].y, result[0].x);
        const marker = new kakao.maps.Marker({
          map: map,
          position: coords
        });
        marker.setMap(map);
        kakao.maps.event.addListener(marker, 'click', function () {
          // 레벨 설정 및 좌표 중심으로 이동
          map.setLevel(3);
          map.setCenter(coords);
        });
        map.setLevel(3);
        map.setCenter(coords);
        const infowindow = new kakao.maps.InfoWindow({
          content: '<div style="width:150px;text-align:center;padding:6px 0;">' + item.title + '</div>'
        });
        infowindow.open(map, marker);
      }
    };
    geocoder.addressSearch(item.address, callback);
  };
리스트에 있는 요소를 클릭 시 실행되는 함수에 지도에 마커를 표시하고 가운대로 나타나게 보여주는 코드를 작성해주었다.
새로운 마커를 그린 것이기 때문에 마커에도 클릭 이벤트를 다시 넣어주어야 했다.
새 api를 사용할 때 마다 문서를 여러번 읽어보고 이해하는게 중요하다는 것을 깨닫는다.. 블로그에서 코드를 그대로 사용하면 해당 코드에 오류가 있을 경우 어쩌피 다시 코드 설계를 해야하기 때문이다.
코드설계를 잘 하기 위해서는 계속 생각해보고 실력을 늘리는 수 밖에 없는 것 같다.
오늘 정보처리기사 필기 시험에 통과했다. 아직 여유가 있으니 주말마다 꾸준히 준비해야 할 것이다.
글이 잘 정리되어 있네요. 감사합니다.