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