본문 바로가기
프로젝트/재활용프로젝트

[재활용 프로젝트][리액트] 카카오 api 분류별 마커 표시 컴포넌트화

by 꽁이꽁설꽁돌 2024. 4. 3.
728x90
반응형

목차

     

    저번 맵 불러오는 것에 이어서 마커들을 버튼을 누를때 마다 종류별로 보이게끔 해보았다.

    그 과정에서 리액트로 컴포넌트화하고 리팩토링한 것을 정리해 보려고 한다.

    타입을 지정해주는 과정에서 많은 것을 배운 것 같다.

     

    완성 모습

    완성되어 버튼을 누를때 마다 바뀌는 모습이다..

    카페 표시 마커
    마트 표시 마커
    주차장 표시 마커

     

    data.ts 

    마커들을 종류별로 저장하기 위해 만든 파일이다.

    import { Position, MarkerProps } from './Marker';
    // 인터페이스를 임포트 받아 상속을 통해 확장한 모습이다.
    export interface DataMarkerProps extends MarkerProps {
      name: string;
    }
    // 이름, 위치, origin을 속성으로 가진다.
    const markers: DataMarkerProps[] = [
      {
        name: 'coffee',
        Positions: [
          { lat: 37.499590490909185, lng: 127.0263723554437 },
          { lat: 37.499427948430814, lng: 127.02794423197847 },
          { lat: 37.498553760499505, lng: 127.02882598822454 },
          { lat: 37.497625593121384, lng: 127.02935713582038 },
          { lat: 37.49646391248451, lng: 127.02675574250912 },
          { lat: 37.49629291770947, lng: 127.02587362608637 },
          { lat: 37.49754540521486, lng: 127.02546694890695 },
        ],
        Origin: { x: 10, y: 0 },
      },
      {
        name: 'store',
        Positions: [
          { lat: 37.497535461505684, lng: 127.02948149502778 },
          { lat: 37.49671536281186, lng: 127.03020491448352 },
          { lat: 37.496201943633714, lng: 127.02959405469642 },
          { lat: 37.49640072567703, lng: 127.02726459882308 },
          { lat: 37.49640098874988, lng: 127.02609983175294 },
          { lat: 37.49932849491523, lng: 127.02935780247945 },
          { lat: 37.49996818951873, lng: 127.02943721562295 },
        ],
        Origin: { x: 10, y: 36 },
      },
      {
        name: 'carpark',
        Positions: [
          { lat: 37.49966168796031, lng: 127.03007039430118 },
          { lat: 37.499463762912974, lng: 127.0288828824399 },
          { lat: 37.49896834100913, lng: 127.02833986892401 },
          { lat: 37.49893267508434, lng: 127.02673400572665 },
          { lat: 37.49872543597439, lng: 127.02676785815386 },
          { lat: 37.49813096097184, lng: 127.02591949495914 },
          { lat: 37.497680616783086, lng: 127.02518427952202 },
        ],
    
        Origin: { x: 10, y: 72 },
      },
    ];
    
    export default markers;

     

    Marker.tsx

    각각의 마커 스타일을 위한 파일이다.

    import { Map, MapMarker } from 'react-kakao-maps-sdk';
    
    // postion 타입 지정
    export interface Position {
      lat: number;
      lng: number;
    }
    
    // 포지션 배열과 origin을 추가한 타입 지정
    export interface MarkerProps {
      Positions: Position[];
      Origin: { x: number; y: number };
    }
    
    const markerImageSrc =
      'https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/category.png';
    const imageSize = { width: 22, height: 26 };
    const spriteSize = { width: 36, height: 98 };
    // 컴포넌트를 담은 함수
    // 컴포넌트는 항상 JSX를 return하기 때문에 JSX.Element로 타입을 지정하면 된다.
    function Marker({ Positions, Origin }: MarkerProps): JSX.Element {
      return (
        <>
          {Positions.map((position) => (
            <MapMarker
              key={`${position.lat},${position.lng}`}
              position={position}
              image={{
                src: markerImageSrc,
                size: imageSize,
                options: {
                  spriteSize,
                  spriteOrigin: Origin,
                },
              }}
            />
          ))}
        </>
      );
    }
    
    export default Marker;

     

    KaKaoMap.tsx

    맵과 마커가 보일 파일이다.

    import React, { useEffect, useRef, useState } from 'react';
    import getGeolocation from 'utils/getGeolocation';
    import { Map, MapMarker } from 'react-kakao-maps-sdk';
    import Marker, { MarkerProps } from './Marker';
    import markers, { DataMarkerProps } from './data';
    
    const { kakao } = window;
    
    export default function KaKaoMap() {
      const { longitude, latitude } = getGeolocation();
      // 초기 배열 기본값
      const FirstMarker: DataMarkerProps[] = markers.filter(
        (category) => category.name === 'coffee',
      );
      const [selectedCategory, setSelectedCategory] =
        useState<DataMarkerProps[]>(FirstMarker);
      // 누른 마커만 표시하는 함수
      function DeleteMarks(name: string) {
        // 원본 배열 가져옴
        setSelectedCategory(markers);
        const newMarkers: DataMarkerProps[] = markers.filter(
          (category) => category.name === name, // 일치하는 배열만 추출
        );
    
        // 배열 재설정
        setSelectedCategory(newMarkers);
      }
    
      return (
        <div>
          <Map
            center={{
              lat: latitude,
              lng: longitude,
            }} // 지도의 중심 좌표
            style={{ width: '100vw', height: '100vh' }} // 지도 크기
            level={3} // 지도 확대 레벨
          >
            {/* 맵 중첩이가능함 */}
            {selectedCategory.map((mark: DataMarkerProps, index: number) => {
              return (
                <Marker
                  key={`${mark.Positions[index].lat},${mark.Positions[index].lng}`}
                  Positions={mark.Positions}
                  Origin={mark.Origin}
                />
              );
            })}
          </Map>
    
          <div className="category">
            {markers.map((mark, index: number) => {
              return (
                <button
                  key={`${mark.Positions[index].lat},${mark.Positions[index].lng}`}
                  type="button"
                  onClick={() => DeleteMarks(mark.name)}
                >
                  {mark.name}
                </button>
              );
            })}
          </div>
        </div>
      );
    }

     

    MapPage.tsx

    맵과 마커를 불러오는 페이지이다.

    import React, { useEffect } from 'react';
    
    import KaKaoMap from 'components/map/KaKaoMap';
    
    export default function MapPage() {
      return (
        <div>
          <KaKaoMap />
        </div>
      );
    }

     

    배운 점

    1. type error

    Uncaught TypeError: Cannot read properties of undefined (reading 'classList')

     

    발견 시 이런식으로 타입을 지정해준다.

     const [selectedCategory, setSelectedCategory] =
        useState<DataMarkerProps[]>(FirstMarker);

     

    2. 타입의 확장 및 타입의 임포트

    // postion 타입 지정
    export interface Position {
      lat: number;
      lng: number;
    }
    
    // 포지션 배열과 origin을 추가한 타입 지정
    export interface MarkerProps {
      Positions: Position[];
      Origin: { x: number; y: number };
    }
    
    export interface DataMarkerProps extends MarkerProps {
      name: string;
    }

     

    3. 배열의 변경 및 불변성 유지(filter 사용)

     function DeleteMarks(name: string) {
        // 원본 배열 가져옴
        setSelectedCategory(markers);
        const newMarkers: DataMarkerProps[] = markers.filter(
          (category) => category.name === name, // 일치하는 배열만 추출
        );
    
        // 배열 재설정
        setSelectedCategory(newMarkers);
      }

     

    참고

    https://velog.io/@nemo/react-error-cannot-read-property

     

    [React 에러 일지] Cannot read properties of undefined...

    Top 10 JavaScript errors from 1000+ projects (and how to avoid them)Uncaught TypeError: Cannot read property ...TypeError: ‘undefined’ is not an objec

    velog.io

    https://www.howdy-mj.me/react/react-node-and-jsx-element

     

    ReactNode vs. JSX.Element 그리고 ReactElement

    해당 글 작성한 시점의 React는 18.0, TypeScript는 2.8 버전이다. - DefinitelyTyped/types/react/index.d.ts ## ReactNode vs. JSX.Element ReactNode, JSX.Element 모두 외부...

    www.howdy-mj.me

     

    반응형