[회고] React - Kakao Map 프로젝트 회고

suno·2023년 1월 31일
0

회고록

목록 보기
8/11
post-thumbnail

쟈-스민

API

기술 스택

  • React
  • React Router Dom
  • Typescript
  • Recoil
  • Firebase Firestore
  • Styled Components
  • React Icons

내가 기여한 것 😙

1. 헤더 / 서점찾기 페이지 UI

map

구현 고민

  • 서점찾기 페이지 screen height 100vh 적용

    1. window 객체의 screenHeight을 상수로 저장해 페이지 높이를 screenHeight - headerHeight로 지정해주었다.
      이렇게 하면 페이지 창 크기를 동적으로 조절할 때 바로바로 반영이 안되는 문제가 발생했다.
      그럼 창 크기 변경에 event listener를 등록하느냐? → 창 크기가 변경 될 때마다 상수를 바꿔주고 css에 변환해서 적용해서 렌더링하고.... 비효율적이라는 생각이 들었다.

    2. css로 해결!
      css 내에서 calc() 메소드를 사용해 계산을 할 수 있었다.
      height: calc(100vh - ${headerHeight}px)
      추가적으로 검색 결과에 스크롤바를 적용하기 위해 overflow-y: auto; 속성을 지정했다.

  • flex-box를 이용한 반응형 레이아웃 적용
    데스크탑에서는 Info와 Map을 좌우로 나란히, 모바일에서는 Info 아래에 Map을 위치했다.


2. 검색결과 페이지네이션

pagination

구현 고민

  • 데이터가 bookstores.ts 파일에 배열로 저장되어 있었기 때문에 페이지네이션을 위해 slice 메소드를 사용했다.
  • loadCount를 변수화하여 더보기 데이터 개수 변경이 용이하도록 했다.

핸들러

const loadCount = 20;
const [countOfData, setCountOfData] = useState<number>(loadCount);

const handleLoadMoreButtonClick = useCallback(() => {
  // 데이터의 끝이라면 isEndOfData 상태를 true로 변경
  if (countOfData + loadCount >= DB.length) {
    setCountOfData(DB.length);
    setIsEndOfData(true);
    return;
  }
  setCountOfData(countOfData + loadCount);
}, [countOfData, DB.length]);

렌더링

// 데이터의 끝이 아닐 때 더보기 버튼 렌더링
{isEndOfData || (
  <S.LoadMoreButton onClick={handleLoadMoreButtonClick}>
    더보기
  </S.LoadMoreButton>
)}

3. 현재 위치 불러와 반경 표시

current location

구현 고민

kakao API를 이용해 반경을 나타냈는데, 내 위치로 검색하기 버튼을 여러번 누르면 반경이 중복으로 렌더링 되는 문제점이 있었다.
그래서 currentCircle state에 현재 반경을 저장하고, state가 존재한다면 null을 할당해 초기화해주는 로직을 추가했다.

핸들러

const location = useGeolocation();

const handleSearchCurrentLocationClick = useCallback(() => {
  // 현재 위치, map이 없다면 return
  if (!location || !map) return;

  navigate('/map');

  // 1. 현재 중심 위치를 kakaoMap 좌표로 변환
  const currentCenter = new window.kakao.maps.LatLng(
    location.coordinates?.lat,
    location.coordinates?.lng,
  );

  // 현재 위치 전역 상태 저장
  setCurrentLocation(location);

  // 2. 이전 반경 표시 삭제
  currentCircle && currentCircle.setMap(null);

  // 3. 필터 초기화 (전체 검색 결과를 바탕으로 필터링 하기 위함)
  handleResetResult();

  // 4. 중심에서 10km 반경 내의 데이터 필터링 후 DB에 저장
  const newDB: any[] = [];
  data.forEach((store) => {
    // 서점의 위치
    const storeLocation = new window.kakao.maps.LatLng(
      store.FCLTY_LA,
      store.FCLTY_LO,
    );

    const poly = new window.kakao.maps.Polyline({
      path: [currentCenter, storeLocation],
    });

    // 서점과 현재 위치의 거리
    const distance = poly.getLength();
    if (distance <= 10000) {
      newDB.push(store);
    }
  });
  setDB(newDB);

  // 5. 현재 위치로 지도 이동
  map.setLevel(8);
  map.panTo(currentCenter);

  // 6. 현재 위치 반경 5km 표시
  const circle = new window.kakao.maps.Circle({
    center: currentCenter,
    radius: 10000,
    strokeWeight: 1,
    strokeColor: BLUE_COLOR,
    strokeOpacity: 0.8,
    fillColor: BLUE_COLOR,
    fillOpacity: 0.2,
  });
  circle.setMap(map);

  // 현재 위치 반경 표시 저장
  setCurrentCircle(circle);
  // eslint-disable-next-line
}, [currentCircle, location, map, setCurrentLocation, handleResetResult, setDB]);

4. 마커에 오버레이 표시

overlay

구현 고민

  • 현재 위치 반경과 마찬가지로 마커를 여러번 클릭하면 오버레이가 중복해서 생성되는 문제점이 있었다.
    currentOverlayStoreId 상태를 저장하여 이전 overlay 상태가 존재한다면 null을 할당했다.

  • 축적 level이 큰 경우, 마커를 클릭했을 때 확대되도록 구현했다. 그 기준을 level 8로 정하고, 8보다 큰 level일 경우 지도를 확대했다.
    만약 level이 8보다 크다면 현재 level을 유지한다.

Overlay 클릭 시 라우팅 (/map/:bookstroeId)

// * bookstoreId가 변경되면 지도 이동
useEffect(() => {
  moveMap(bookstoreId);
  // eslint-disable-next-line
}, [bookstoreId, map]);

핸들러

// * 라우터 파라미터로 받은 bookstoreId 값에 따라 지도 이동
const moveMap = useCallback(
  (bookstoreId: string | undefined) => {
    // 지도가 생성되지 않았으면 함수 종료
    if (!map) return;

    // 1. 이전에 클릭한 마커가 있으면 지도에서 제거
    const prevOverlay = currentOverlayStoreId;
    prevOverlay && prevOverlay.setMap(null);

    // 2. 클릭한 bookstoreId에 해당하는 마커로 지도 이동
    DB.forEach((store) => {
      if (store.ESNTL_ID === bookstoreId) {
        const moveLatLon = new kakao.maps.LatLng(
          store.FCLTY_LA,
          store.FCLTY_LO,
        );

        // 지도 확대 레벨 설정
        if (map.getLevel() > 8) {
          map.setLevel(8);
        }
        map.panTo(moveLatLon); // 지도 중심 좌표 이동

        // 3. 커스텀 오버레이 생성
        const overlayContent = ReactDOMServer.renderToString(
          <Overlay info={store} />,
        );
        const overlay = new kakao.maps.CustomOverlay({
          content: overlayContent,
          map: map,
          position: moveLatLon,
          xAnchor: 0.5,
          yAnchor: 0.5,
        });

        setCurrentOverlayStoreId(overlay);
      }
    });
  },
  [DB, map, currentOverlayStoreId],
);

느낀점

이번 프로젝트는 처음부터 퀄리티에 집중하기로 팀원들과 협의하고 시작했다. 그래서 구현 기능의 가지수에 부담을 가지지 않고 작성한 코드를 여러번 보며 리팩토링 하는 경험을 할 수 있었다.

발표회 때 튜터님께서도 잡다한 기능보다 핵심 기능으로 구성된게 기획적으로 좋아 보였다고 말씀해주셨다. 또 다른 팀 분들도 실제 서비스 해도 괜찮겠다고 반응해주셔서 ㅎㅎ.. 굉장히 뿌듯했다!

항상 느끼지만 나는 다른 사람 코드를 피드백하고 설명해주는게 참 즐겁다. 내가 도움이 되는 것도 좋고, 다양항 로직을 접하고 생각해볼 수 있어 스스로도 많은 도움을 받는다.

하지만 프로젝트가 끝날때마다 아쉬운 점은,, 코드를 작성하기 전에 설계를 꼼꼼히 해야한다는 것이다. 머리보다 손이 먼저 나가는 편이라 구현 속도가 빠르긴 하지만, 완성하고 보면 그게 최선이 아닐 때가 많다. 중간중간 피드백도 받고, 스스로 로직을 점검해볼 필요가 있다.

Keep

  • 새로운 기술을 도전해보자. (이번에는 recoil을 사용해 봄.)
  • 팀원들과 코드 피드백을 많이 하자.
  • GitHub/Slack 등 협업 툴을 적극적으로 사용하자.
    이번에 GitHub Issue-PR-Review-Merge 과정을 경험했고, Slack에 GitHub 알림봇을 추가해서 생산성을 높였다.

Problem

  • 구현 과정을 문서로 잘 기록하기. 단순히 코드가 아닌 고민한 과정과 시행착오를 담자.
  • 대충 안다고 넘어가지 말고 개념을 확실히 하자. (리액트 훅, 리덕스, 리코일 등등)

Try

  • 다음 프로젝트에서는 GitHub Wiki, Issue를 활용해 보자.
  • 프로젝트를 포트폴리오에 담을 수 있을 정도의 퀄리티로 리팩토링 해보자.
profile
Software Engineer 🍊

0개의 댓글