Closed swjo207 closed 1 year ago
일단 해당 문제점은 kakao 객체는 SSR시점에서는 접근이 불가능한 객체 입니다.
해당 문제점의 힌트는 본문에 작성하신 declare global { module Window { kakao: any; } }
입니다.
nextjs에서는 아마 kakao.maps.d.ts
를 설치하였어도, kakao.maps
형태로 직접 접근하는 것이 타입 추론을 해주지 않고, window.kakao.maps
로 접근해야 합니다. 아마 kakao.maps
declare 선언을 하신 이유가 해당 사실에 대해서 모르고 있으셨던 것 같습니다. 다시 본론으로 돌아와서 window.kakao.maps
에서 접근하는 것을 nextjs에서 강제한 이유는 Client시점에서만 접근이 가능한 객체라는 것을 인지 시키기 위해서 입니다. Script
를 이용해서 load를 하거나, useInjectKakaoMapApi
hook를 이용하여 load를 하게 되면 외부에서 js를 로드하여 브라우저에서 실행을 시켜, window.kakao.map
에 객체를 추가를 하게 됩니다.
이때 page.tsx
에 kakao.maps.drawing.OverlayType
의 경우 단순한 type이 아닌 TypeScript의 특이한 객체인 enum입니다.
일반적으로 TypeScript에서 새로 추가된 문법은 단순하게 트랜스파일 시점에만 활용하는 부가 정보를 담은 문법이지만, enum의 경우 실제 JavaScript로 생성되는 코드에 추가적으로 객체를 생성하는 문법입니다.
참고: https://www.typescriptlang.org/ko/docs/handbook/enums.html
따라서 해당 Type정의의 경우 실제 객체가 있다는 것을 가정하고 작성하였기 때문에 windows.kakao.maps
가 로딩 되기 전까지는 접근이 불가능한 객체이므로 런타임에서 오류가 발생하는 것 입니다.
이를 해결하기 위해서는 몇가지 방법이 있습니다.
첫번째로는 Map
컴포넌트의 children
으로 넣는 객체의 경우 라이브러리 내부적으로 kakao.maps
가 로딩완료된 이후에 렌더링 하도록 되어 있습니다. 이를 이용하여, kakao.maps.drawing.OverlayType
를 사용하는 부분을 다른 컴포넌트로 분리하여 작성하게 되면 문제가 해결 됩니다.
두번째로는 useInjectKakaoMapApi
혹인 next.js에서 제공하는 Script
에서 onLoad
등의 이벤트를 활용하여, kakao.maps가 로딩이 끝났는지를 체크하여, 렌더링을 하는 것을 직접 제어 하는 것 입니다.
마지막 방법은 제가 지원을 해야 하는 방법입니다. enum의 경우 객체가 있어야 한다는 문제점을 해결하기 위해, 내부 값을 추론할 수 있도록 string literal union type을 추가하여 지원하는 방법입니다. 문제점은 kakao측에서 내부에서 사용하는 객체의 값이 변경된 경우 즉시 enum 객체값을 1대1로 대응시킬 수 없기 때문에 변경이 발생하면 오류가 발생할 수 있습니다.
마지막 세번째 방법에 대해서 코드를 다시확인 해본 결과 내부적인 값이 변경될 일이 없을 것 같아서 해당 부분 작업하여, 추후 다음 버전에 반영하도록 하겠습니다.
Next JS (13.2.4) 마이그레이션 하다가, 우연히 발견하게 되었습니다.
DrawingManager를 사용해야 하는데, 계속 문제에 부딪쳐서 한번 여쭤보고자 합니다. https://react-kakao-maps-sdk.jaeseokim.dev/docs/sample/library/basicDrawingLibrary 샘플을 /app/map/page.tsx 에서 복사해서 띄워 보는데, TypeError: Cannot read properties of undefined (reading 'OverlayType') 타입 에러가 뜨고 진전이 없습니다.
tsconfig.json 파일에,
"types": [ "kakao.maps.d.ts" ]
가 설정되어 있으며, vscode 에디터에서도 OverlayType이 레퍼런싱이 되는 걸로 봐서는 문제가 없을 것 같은데,
테스트한 소스는 다음과 같습니다.
'use client' import Script from "next/script" import { useRef } from "react" import { Map, DrawingManager } from "react-kakao-maps-sdk"
declare global { interface Window { kakao: any; } }
let map_src =
//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_MAP_API_KEY}&autoload=false&libraries=drawing,services,clusterer
;export default function MapEdit() { // ref 객체를 통해 kakao.maps.drawng.DrawingManager 객체를 전달 받아 사용합니다. // 또한 TypeScript를 사용하기 떄문에 전달 받는 DrawingManager에서 사용하는 OverlayType에 대해서 정의해야 합니다. const managerRef = useRef< kakao.maps.drawing.DrawingManager< | kakao.maps.drawing.OverlayType.ARROW | kakao.maps.drawing.OverlayType.CIRCLE | kakao.maps.drawing.OverlayType.ELLIPSE | kakao.maps.drawing.OverlayType.MARKER | kakao.maps.drawing.OverlayType.POLYLINE | kakao.maps.drawing.OverlayType.RECTANGLE | kakao.maps.drawing.OverlayType.POLYGON
}