[TIL] 230720

이세령·2023년 7월 20일
0

TIL

목록 보기
60/118

무한 스크롤

팀원분이 무한 스크롤을 구현하셨는데, 사용법을 정리해보려고 한다.

예제

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')
);
  • 먼저, 예제를 살펴보면 IntersectionObserver 객체를 생성하고, callback함수와 options를 전달한다.
  • observe로 가질 target element를 추가한다.
  • target element가 정의해둔 threshold만큼 화면에 노출/제외 되면 entries배열에 추가하고, callback함수를 호출한다.
  • 화면의 노출여부를 전달받은 entries 배열을 확인하면서 isIntersecting으로 확인한다.
  • target element를 제거하는 방법은 unobserve로 제거한다.

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를 사용할 때 마다 문서를 여러번 읽어보고 이해하는게 중요하다는 것을 깨닫는다.. 블로그에서 코드를 그대로 사용하면 해당 코드에 오류가 있을 경우 어쩌피 다시 코드 설계를 해야하기 때문이다.
코드설계를 잘 하기 위해서는 계속 생각해보고 실력을 늘리는 수 밖에 없는 것 같다.
오늘 정보처리기사 필기 시험에 통과했다. 아직 여유가 있으니 주말마다 꾸준히 준비해야 할 것이다.

profile
https://github.com/Hediar?tab=repositories

1개의 댓글

comment-user-thumbnail
2023년 7월 20일

글이 잘 정리되어 있네요. 감사합니다.

답글 달기