Skip to content

Commit

Permalink
[FE][Refactor] #224 : 지도 컴포넌트 리팩토링
Browse files Browse the repository at this point in the history
- 코드 가독성 개선
- 에러 케이스 추가
- 타입 분리
  • Loading branch information
effozen committed Nov 22, 2024
1 parent 66099e0 commit df7cc79
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 80 deletions.
41 changes: 16 additions & 25 deletions frontend/src/component/maps/Map.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
import { NaverMap } from '@/component/maps/NaverMap.tsx';
import { ReactNode, useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import { NaverMap } from '@/component/maps/NaverMap.tsx';
import { IMapObject, IMapOptions, IMapRefMethods } from '@/component/maps/Map.types.ts';
import classNames from 'classnames';

type IMapObject = naver.maps.Map | null;

export interface IMapOptions {
lat: number;
lng: number;
zoom?: number;
}
const validateKindOfMap = (type: string) => ['naver'].includes(type);

interface IMapProps extends IMapOptions {
className?: string;
type: string;
initMap: (mapObject: IMapObject) => void;
}

// 부모 컴포넌트가 접근할 수 있는 메서드들을 정의한 인터페이스
export interface IMapRefMethods {
getMapObject: () => naver.maps.Map | null;
getMapContainer: () => HTMLElement | null;
onMouseClickHandler: (event?: React.MouseEvent) => void;
}

const validateKindOfMap = (type: string) => ['naver'].includes(type);

export const Map = forwardRef<IMapRefMethods, IMapProps>((props, ref) => {
if (!validateKindOfMap(props.type)) throw new Error('Invalid map type');
if (!validateKindOfMap(props.type))
throw new Error('🚀 지도 로딩 오류 : 알 수 없는 지도 타입이 인자로 들어 왔습니다.');

const mapRef = useRef<IMapRefMethods | null>(null);
const mapRefMethods = useRef<IMapRefMethods | null>(null);
const mapContainer = useRef<HTMLElement | null>(null);
const [mapObject, setMapObject] = useState<IMapObject>(null);

const [mapObject, setMapObject] = useState<IMapObject>();
const [MapComponent, setMapComponent] = useState<ReactNode>();

const onMapInit = (mapObj: IMapObject) => {
Expand All @@ -44,21 +32,24 @@ export const Map = forwardRef<IMapRefMethods, IMapProps>((props, ref) => {
lat={props.lat}
lng={props.lng}
zoom={props.zoom}
ref={mapRef}
ref={mapRefMethods}
onMapInit={onMapInit}
/>
);
setMapComponent(mapComponent);
}
}, [props.lat, props.lng, props.zoom, props.type]);
}, []);

useEffect(() => {
mapContainer.current = mapRef.current?.getMapContainer() ?? null;
props.initMap(mapObject);
mapContainer.current = mapRefMethods.current?.getMapContainer() ?? null;
if (mapObject) props.initMap(mapObject);
}, [mapObject]);

useImperativeHandle(ref, () => ({
getMapObject: () => mapObject,
getMapObject: () => {
if (mapObject) return mapObject;
throw new Error('🚀 지도 로딩 오류 : 지도 객체가 존재하지 않습니다.');
},
getMapContainer: () => mapContainer.current,
onMouseClickHandler: () => {},
}));
Expand Down
42 changes: 42 additions & 0 deletions frontend/src/component/maps/Map.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export type IMapObject = naver.maps.Map;

export type IMapLatLngBound = naver.maps.LatLngBounds;

export interface IMapOptions {
lat: number;
lng: number;
zoom?: number;
}

/**
* Forward Ref 를 통해서, 부모에서 자식 컴포넌트의 Ref에 접근할 때 쓰이는 인터페이스.
* 다음과 같은 목적으로 쓰인다.
* 1. 지도 컨테이너 요소와, 지도 객체를 가져온다.
* 2. 지도 객체의 이벤트 위임을 위해 자신을 컨트롤 할 수 있는 Handler를 부모에게 전달하는 역할을 한다.
* 이렇게 전달받은 핸들러로 부모 컴포넌트에서 자식에 있는 지도 객체를 컨트롤 할 수 있다.
* */
export interface IMapRefMethods {
getMapObject: () => naver.maps.Map | null;
getMapContainer: () => HTMLElement | null;
onMouseClickHandler: (event?: React.MouseEvent) => void;
}

// lat: 위도(y), lng: 경도(x) INaverMapVertexPosition
export interface IMapVertexCoordinate {
ne: {
lng: number;
lat: number;
};
nw: {
lng: number;
lat: number;
};
se: {
lng: number;
lat: number;
};
sw: {
lng: number;
lat: number;
};
}
25 changes: 16 additions & 9 deletions frontend/src/component/maps/NaverMap.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { setNaverMapSync } from '@/component/maps/naverMapUtils.ts';
import { IMapOptions, IMapRefMethods } from '@/component/maps/Map.tsx';
import { IMapOptions, IMapRefMethods } from '@/component/maps/Map.types.ts';

interface INaverMapProps extends IMapOptions {
onMapInit: (map: naver.maps.Map) => void; // 콜백 프로퍼티 추가
}

export const NaverMap = forwardRef<IMapRefMethods, INaverMapProps>((props, ref) => {
const naverMapObject = useRef<naver.maps.Map | null>(null);
const naverMapContainer = useRef<HTMLElement | null>(null);
const mapObject = useRef<naver.maps.Map | null>(null);
const mapContainer = useRef<HTMLElement | null>(null);

const [mapOptions, setMapOptions] = useState<IMapOptions>({
lat: props.lat,
lng: props.lng,
Expand All @@ -24,17 +25,23 @@ export const NaverMap = forwardRef<IMapRefMethods, INaverMapProps>((props, ref)
}, [props.lat, props.lng, props.zoom]);

useEffect(() => {
if (naverMapContainer.current && mapOptions !== null) {
naverMapObject.current = setNaverMapSync(naverMapContainer.current, mapOptions);
if (naverMapObject.current !== null) props.onMapInit(naverMapObject.current); // 콜백 호출
if (mapContainer.current && mapOptions) {
mapObject.current = setNaverMapSync(mapContainer.current, mapOptions);
if (mapObject.current) props.onMapInit(mapObject.current); // 콜백 호출
}
}, [mapOptions]);

useImperativeHandle(ref, () => ({
getMapObject: () => naverMapObject.current,
getMapContainer: () => naverMapContainer.current,
getMapObject: () => {
if (mapObject) return mapObject.current;
throw new Error('🚀 지도 로딩 오류 : 지도 객체가 존재하지 않습니다.');
},
getMapContainer: () => {
if (mapContainer) return mapContainer.current;
throw new Error('🚀 지도 로딩 오류 : 지도 컨테이너가 존재하지 않습니다.');
},
onMouseClickHandler: () => {},
}));

return <section ref={naverMapContainer} className="h-full w-full" />;
return <section ref={mapContainer} className="h-full w-full" />;
});
32 changes: 32 additions & 0 deletions frontend/src/component/maps/mapUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { IMapVertexCoordinate, IMapObject, IMapLatLngBound } from '@/component/maps/Map.types.ts';

export const getMapVertexCoordinate = (map: IMapObject): IMapVertexCoordinate => {
let bounds: IMapLatLngBound;

if (map instanceof naver.maps.Map) {
bounds = map.getBounds() as naver.maps.LatLngBounds;
} else {
throw new Error('🚀 꼭지점 좌표 가져오기 오류 : 지도 객체가 없습니다.');
}

const sw = bounds.getSW();
const ne = bounds.getNE();
return {
se: {
lng: ne.lng(),
lat: sw.lat(),
},
sw: {
lng: sw.lng(),
lat: sw.lat(),
},
ne: {
lng: ne.lng(),
lat: ne.lat(),
},
nw: {
lng: sw.lng(),
lat: ne.lat(),
},
};
};
47 changes: 1 addition & 46 deletions frontend/src/component/maps/naverMapUtils.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,4 @@
import { IMapOptions } from '@/component/maps/Map.tsx';

// lat: 위도(y), lng: 경도(x)
export interface INaverMapVertexPosition {
ne: {
lng: number;
lat: number;
};
nw: {
lng: number;
lat: number;
};
se: {
lng: number;
lat: number;
};
sw: {
lng: number;
lat: number;
};
}

export const getNaverMapVertexPosition = (map: naver.maps.Map): INaverMapVertexPosition => {
const bounds = map.getBounds() as naver.maps.LatLngBounds;
const sw = bounds.getSW();
const ne = bounds.getNE();
return {
se: {
lng: ne.lng(),
lat: sw.lat(),
},
sw: {
lng: sw.lng(),
lat: sw.lat(),
},
ne: {
lng: ne.lng(),
lat: ne.lat(),
},
nw: {
lng: sw.lng(),
lat: ne.lat(),
},
};
};
import { IMapOptions } from '@/component/maps/Map.types.ts';

export const setNaverMapOption = (mapOptions: IMapOptions): IMapOptions => {
return {
Expand All @@ -53,7 +9,6 @@ export const setNaverMapOption = (mapOptions: IMapOptions): IMapOptions => {
};
};

// utils에 있는 일반 함수로 사용
export const setNaverMap = (
htmlElement: HTMLElement,
mapOptions: IMapOptions,
Expand Down

0 comments on commit df7cc79

Please sign in to comment.