ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Blazor에서 카카오 지도 API 적용하기
    HelloJkw 개발 2022. 3. 5. 14:16

    카카오 지도 API 개발하는 과정에서 겪은 일.

    blazor를 선택하면서 js를 쓸 일은 없겠거니 했지만,

    js로만 되어있는 라이브러리를 사용하게 되면 어쩔 수 없어 JS Interop을 사용해서 개발해야 한다.

    처음에는 단순 함수로 개발했다.

    ts로 바꾸는 등 변화 과정을 기록한다.

     

    blazor project에 typescript를 적용하는 과정은 여기서 확인.

    2022.03.04 - [분류 전체보기] - Blazor에서 typescript 사용하기

     

     

    변경 전: javascript 기반, 단순 함수를 호출

    javascript에 함수로 만들고, 전역변수를 두어 map 객체를 관리한다.

    <문제점>

    모든 변수는 전역변수로 만든다. (당연히)

    map이외에 marker 등 다른 함수와 섞이면서 코드 관리가 어렵다.

    변수의 오너십이 없다. 누가 map변수를 바꿔놓을지 모른다.

    map을 하나만 만들 수 있다.

    map을 여러개 지원하려면 var map; 대신 var maps = {} 으로 바꾸고,

    maps[mapId] = map; 으로 처리한 후,

    모든 함수에서 mapId를 넘겨받아와서 map을 찾아야 하는 구조로 만들어야 한다.

    그렇다고 오너십이 해결되는 건 아니다.

     

    [kakaomap.js]

    var map;
    var dotnetInstance;
    
    export function createMap(mapId, instance) {
        dotnetInstance = instance;
    
        var container = document.getElementById(mapId);
        var options = {
            center: new kakao.maps.LatLng(33.450701, 126.570667),
            level: 3 //지도의 레벨(확대, 축소 정도)
        };
    
        map = new kakao.maps.Map(container, options);
    }
    
    export function setCenter(position) {
        const center = new kakao.maps.LatLng(position.latitude, position.longitude);
        map.setCenter(center);
    }
    
    export function getCenter() {
        map.getCenter();
    }
    
    function onMapClicked(mouseEvent) {
        dotnetInstance.invokeMethodAsync('OnMapClicked', mouseEvent);
    }
    
    export function addClickEvent() {
        kakao.maps.event.addListener(map, 'click', onMapClicked);
    }

    [KakaoMap.cs]

    public class KakaoMap : IKakaoMap
    {
        private IJSRuntime JS;
        private DotNetObjectReference<KakaoMap> _kakaoMapRef;
        private IJSObjectReference _module;
    
        public async ValueTask CreateMap(string mapId)
        {
            _kakaoMapRef = DotNetObjectReference.Create(this);
            var jsPath = "./_content/BlazorKakaoMap/js/kakaomap.js";
            _module = await JS.InvokeAsync<IJSObjectReference>("import", jsPath);
            await _module.InvokeVoidAsync("createMap", mapId, _kakaoMapRef);
        }
    
        public async ValueTask SetCenter(LatLng position)
        {
            await _module.InvokeVoidAsync("setCenter", position);
        }
    
        public async ValueTask<LatLng> GetCenter()
        {
            var center = await _module.InvokeAsync<LatLng>("getCenter");
            return center;
        }
    
        private event EventHandler<MouseEvent> _clicked;
        public event EventHandler<MouseEvent> Clicked
        {
            add
            {
                // js에 click event를 등록한다.
                _module.InvokeVoidAsync("addClickEvent");
                _clicked += value;
            }
            remove
            {
                _clicked -= value;
            }
        }
    
        [JSInvokable]
        public void OnMapClicked(MouseEvent mouseEvent)
        {
            // 지도를 클릭하면 js의 onMapClicked함수를 통해서 불린다.
            _clicked?.Invoke(this, mouseEvent);
        }
    }

    변경 후: typescript, 객체 지향으로.

    typescript를 적용하였고,

    map을 객체 하나로 만들었다.

    C# 에서도 map을 하나의 객체로 받아들였다. IJSObjectReference를 활용하면 간단하다.

    js를 import한 module을 받는것이 아니고, map객체를 할당한다.

    이렇게 변경하면 여러 map을 만들더라도 객체의 관리를 .net에서 하게 된다.

    js에도 전역변수를 없앨 수 있다.

     

    [kakaomap.ts]

    declare var kakao: any;
    
    export function createMap(mapId, instance) {
        return new Map(mapId, instance);
    }
    
    class Map {
        private map: any;
        private dotnetInstance: any;
    
        constructor(mapId: string, instance) {
            this.dotnetInstance = instance;
            const container = document.getElementById(mapId);
            const options = {
                center: new kakao.maps.LatLng(33.450701, 126.570667), //지도의 중심좌표.
                level: 3 //지도의 레벨(확대, 축소 정도)
            };
    
            this.map = new kakao.maps.Map(container, options);
        }
    
        onMapClicked(mouseEvent) {
            this.dotnetInstance.invokeMethodAsync('OnMapClicked', mouseEvent);
        }
    
        addClickEvent() {
            kakao.maps.event.addListener(this.map, 'click', this.onMapClicked.bind(this));
        }
        
        setCenter(position) {
            const center = new kakao.maps.LatLng(position.latitude, position.longitude);
            this.map.setCenter(center);
        }
    
        getCenter() {
            return this.map.getCenter();
        }
    }

     

    [kakaomap.cs]

    public class KakaoMap : IKakaoMap
    {
        private IJSRuntime JS;
        private DotNetObjectReference<KakaoMap> _kakaoMapRef;
        private IJSObjectReference _map;
    
        public async ValueTask CreateMap(string mapId)
        {
            _kakaoMapRef = DotNetObjectReference.Create(this);
            var jsPath = "./_content/BlazorKakaoMap/js/kakaomap.js";
            var module = await JS.InvokeAsync<IJSObjectReference>("import", jsPath);
            // kakao map 객체를 가지고 있는다.
            _map = await module.InvokeAsync<IJSObjectReference>("createMap", mapId, _kakaoMapRef);
        }
    
        public async ValueTask SetCenter(LatLng position)
        {
            // map 객체의 함수를 바로 호출한다.
            // 다른 것을 침범할 수도, 침범당하지도 않는다.
            await _map.InvokeVoidAsync("setCenter", position);
        }
    
        public async ValueTask<LatLng> GetCenter()
        {
            var center = await _map.InvokeAsync<LatLng>("getCenter");
            return center;
        }
    
        private event EventHandler<MouseEvent> _clicked;
        public event EventHandler<MouseEvent> Clicked
        {
            add
            {
                // js에 click event를 등록한다.
                _map.InvokeVoidAsync("addClickEvent");
                _clicked += value;
            }
            remove
            {
                _clicked -= value;
            }
        }
        
        [JSInvokable]
        public void OnMapClicked(MouseEvent mouseEvent)
        {
            // 지도를 클릭하면 js의 onMapClicked함수를 통해서 불린다.
            _clicked?.Invoke(this, mouseEvent);
        }
    }

     

     

    이 코드가 최종 모습은 아니다.

    event 할당도 저렇게 간단하지 않고, 제대로 동작하기 위한 장치가 더 있다.

    하지만 전반적인 흐름을 보기 위해서 이 정도로 정리했다.

     

    typescript로 개발하고,

    객체화 시켜서 객체 자체를 .net에 할당하는 면 깔끔하게 개발 할 수 있는 것 같다.

     

    여러 js 파일을 가지고 개발할 때는 어떤 이슈가 생길지 모르겠다.

    marker를 다른 파일로 생성해서 테스트 해볼 예정.

     

     

    댓글 0

Designed by Tistory.