본문 바로가기

Sparta x 이노베이션 캠프/팀 프로젝트

TIL : 카카오맵 지도 범위 재설정 하기 (Trouble Shooting, 코드리팩토링 ver.2)

반응형

내가 짠 코드를 리펙토링하면서 실전프로젝트에서 쓰는 카카오맵을 지도 범위 재설정을 해주었다. 예전 코드는 맵이 보여지는 컨테이너를 삼항연산자를 이용해 어떤 경우에 무슨 좌표를 보여 줄 지를 결정했다. 

 

예전 맵 예외처리에 관한 포스팅

 

TIL : React 카카오 api 맵 예외처리, 카카오맵 중심좌표 이동시키기

실전 프로젝트 3주차. 벌써 반이나 지나갔다. 개발하고있는 어플리케이션은 여행후기공유 플랫폼이고 Minimum Viable Product (MVP)는 1. 소셜로그인 (네이버, 카카오) 2. 회원가입시 여행관련 관심사 선

nonjee888.tistory.com

 

예전에 맵을 보여주는 방식은 일일이 center 좌표를 데이터에서 뽑아내서 찍어주는 방식이었다면 이번 리펙토링은 라이브러리에서 제공하는 샘플을 십분 활용 해서 자동화(?)를 해 보는 방식으로 선택했다. 그쪽이 훨씬 코드도 짧아지고 가독성이 좋아 지지 않을까 해서.

 

다음은 게시글을 등록하는 페이지의 모습.

 

 

구현하고자 하는 서비스는 사용자가 게시글을 업로드 할 때, 장소를 검색해서 그 주변에 마커와 경로를 찍고 다른 사용자들과 자신의 여행 경로를 공유하는 것이었다.

 

그러려면 크게

1. 키워드로 장소 검색이 가능해야하고

2. 자신의 여행경로를 맵 위에 표시 가능해야하고

3. 그것을 게시글 상세보기를 할 때 표시해 주어야 한다.

 

이 부분이 기획단계에서 모두 토의하고 만들어졌어야 했는데 만들다가 추가하게 되어서 백엔드 분들이 고생을 많이하셨다. 죄송함니다...

 

라이브러리 안에 키워드로 장소 검색하기, Drawing 라이브러리 사용하기, Drawing 라이브러리에서 데이터 얻기까지는 커스터마이징을 해서 맵에 적용했고 이번에 게시글 상세보기지도 범위 재설정하기 라는 샘플 코드를 이용했다. 

 

리팩토링 후 게시글 상세보기 부분 코드 

// PostDetail.jsx 맵 부분 코드 

import "./style.scss";

import { useEffect, useState, useMemo, useRef } from "react";
import { instance } from "../../shared/api";
import { useParams, useNavigate } from "react-router";
import { onLikePost } from "../../redux/modules/posts";
import { useDispatch, useSelector } from "react-redux";
import { getDetailPosts } from "../../redux/modules/posts";

import { Map, Polyline, MapMarker } from "react-kakao-maps-sdk";
import Swal from "sweetalert2";
import dompurify from "dompurify";
import PostComment from "./PostComment";
import heart from "../../asset/heart.png";
import edit from "../../asset/edit.png";
import report from "../../asset/report.png";
import listIcon from "../../asset/assetFooter/listIcon.png";
import deleteimg from "../../asset/deleteimg.png";

const { kakao } = window;

const PostDetail = () => {
  const { id } = useParams();
  const { isLoading, error, detail } = useSelector((state) => state?.posts);

  const [user, setUser] = useState();
  const [center, setCenter] = useState();
  const [poly, setPoly] = useState();
  const [overlayData, setOverlayData] = useState({
    marker: [],
    polyline: [],
  });

  const sanitizer = dompurify.sanitize;
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const writerId = detail.nickname;
  const userConfirm = user === writerId;

  const fetch = async () => {
    const { data } = await instance.get(`/api/detail/${id}`);

    setCenter(data?.data?.mapData?.marker);  //user가 입력한 마커 좌표 center에 저장
    setPoly(data?.data?.mapData?.polyline);  //user가 입력한 폴리라인 좌표 poly에 저장
    setUser(data?.data?.nickname);
  };

  useEffect(() => {
    fetch();
  }, []);

  const mapRef = useRef();
  const bounds = useMemo(() => {
    const bounds = new kakao.maps.LatLngBounds(); 
    
	//폴리라인만 입력 된 경우 폴리라인의 좌표를 기준으로 중심 이동
    if (center?.length === 0 && poly[0]?.points.length !== 0) { 
      poly[0]?.points?.forEach((point) => {
        bounds.extend(new kakao.maps.LatLng(point.y, point.x));
      });
      
    //마커만 입력 또는 마커+폴리라인 입력 시 마커를 기준으로 중심 이동 
    } else {
      center?.forEach((point) => {
        bounds.extend(new kakao.maps.LatLng(point.y, point.x));
      });
    }
    return bounds;
  }, [center, poly]);

  function pointsToPath(points) {
    return points.map((point) => ({
      lat: point.y,
      lng: point.x,
    }));
  }

  useEffect(() => {
    if (id !== undefined) {
      dispatch(getDetailPosts(id)).then((response) => {
        setOverlayData(response.payload.mapData); //overlayData에 user가 입력한 여행경로 저장해서 라인으로 그려줌
      });
    }
  }, [dispatch, id]);

  if (isLoading) {
    return <div>...로딩중</div>;
  }
  if (error) {
    return <div>{error.message}</div>;
  }
  
  ... 생략 ...
  
  return(
  <>
  <div className="map-wrapper">
              
            //데이터 늦게 들어와서 undefined 오류방지용 & 맵을 이용하지 않은 user 예외 처리   
              
            {center === undefined && poly === undefined ? (
              <> {detail.nickname}님은 경로를 공유하지 않았습니다. </>
            ) : center?.length === 0 && poly?.length === 0 ? (
              <> {detail.nickname}님은 경로를 공유하지 않았습니다. </>
            ) : (
              <>
                <button
                  className="map-detail"
                  onClick={() => {
                    const map = mapRef.current;
                    if (map) map.setBounds(bounds);
                  }}
                >
                  눌러서경로보기
                </button>
                <Map // 지도를 표시할 Container
                  center={{
                    // 지도의 중심좌표
                    lat: 33.450701,
                    lng: 126.570667,
                  }}
                  style={{
                    width: "100%",
                    height: "300px",
                  }}
                  level={4} // 지도의 확대 레벨
                  ref={mapRef}
                >
                  {center?.map((point) => (
                    <MapMarker
                      key={`${point.y}-${point.x}`}
                      position={{ lat: point.y, lng: point.x }}
                    />
                  ))}

                  {overlayData.polyline.map(({ points, options }, i) => (
                    <Polyline
                      key={i}
                      path={pointsToPath(points)}
                      {...options}
                    />
                  ))}
                </Map>
              </>
            )}
          </div>
  </>
  
  ); 
};

예전에 맵 부분에서 삼항연산자로 예외처리 해주느라 맵 컴포넌트를 세번 네번 반복 해준 것에 비하면 코드량이 훨씬 줄어들었다. 지도 범위 재설정을 위해서 조건식을 세우는 부분을 함수에서 처리해주니 맵 렌더링 부분 코드가 훨씬 가독성이 좋아졌음.

 

결과

1. 마커만 사용해서 여러 군데 찍음

2. 폴리라인만으로 경로 표시

3. 마커 + 폴리

4. 지도 안씀

모두 오류없이 잘 나오는 것을 확인 할 수 있다.

이 소스코드는 원래 눌러서 지도 범위를 재설정 하는 버튼이 포함되어있는데, 이 라이브러리를 쓰신 다른 조원 분은 버튼 온클릭 안에 있는  

 

 const map = mapRef.current
 if (map) map.setBounds(bounds)

 

이 부분을 useEffect로 실행해 상세페이지가 렌더링 되자마자 해당 좌표로 지도범위가 자동 재설정 되도록 구현하였는데, 나의 경우에는 랜덤으로 자동 재설정이 되었다가, 새로고침을 해야 재 설정이 되거나 아예 재설정이 되지 않아서 버튼을 그대로 사용해줬다. 

이 또한 다시 리펙토링..... 해보아야겠다.

 

소감

하고 싶었던 맵 리펙토링을 100% 만족은 아니지만 해 볼수 있어서, 그리고 적용이 잘 된 것 같아서 좋다. 라이브러리는 정말 편리하다. 개발하신 분들께 경의를 표함.


버튼 없애기 성공! 트러블 슈팅에서 카카오 맵 부분에 정리되어 있습니다 :)

# 버튼 없이 지도 범위 재설정 코드 리펙토링(3)

 

버튼 적용 전과 후 비교

 

버튼 없어지기 전vs후

 

반응형