Mapbox GL JS를 React의 함수 구성 요소로 표시

소개



이전에 투고한 React의 함수 구성 요소에서 Mapbox GL JS를 표시하는 데모 라고 하는 것이, 비교적 담백한 any를 사용하고 있거나 데모라고 말할 수 있을지 수상한 할 수 있었으므로, 그 리바이스를 겸해, 보다 넉넉한 샘플 구현을 나타냅니다.

조건


  • React Function Component + hooks
  • TypeScript

  • 환경 구축



    ①React+Typescript 프로젝트 구축
    npx create-react-app react-mapbox-demo --template typescript
    

    ※템플릿의대로라면 React의 형태 정의가 잘 적용되지 않을지도, 그 경우 다시 npm install 하면 괜찮습니다.

    ②Mapbox GL JS와 형 정의를 인스톨
    npm install [email protected] @types/mapbox-gl
    

    ③서버 세우다
    npm start
    

    구성 요소 샘플


    import React, { useEffect, useState, useRef } from 'react';
    
    import mapboxgl from 'mapbox-gl';
    import 'mapbox-gl/dist/mapbox-gl.css';
    
    // Mapbox Style
    const mapStyle: mapboxgl.Style = {
        version: 8,
        sources: {
            OSM: {
                type: 'raster',
                tiles: ['http://tile.openstreetmap.org/{z}/{x}/{y}.png'],
                tileSize: 256,
                attribution:
                    '<a href="http://osm.org/copyright">© OpenStreetMap contributors</a>',
            },
        },
        layers: [
            {
                id: 'OSM',
                type: 'raster',
                source: 'OSM',
                minzoom: 0,
                maxzoom: 18,
            },
        ],
    };
    
    const Map: React.FC = () => {
        // mapboxgl.Mapのインスタンスへの参照を保存するためのuseState
        const [mapInstance, setMapInstance] = useState<mapboxgl.Map>();
    
        // 地図表示するDiv要素を特定するためのuseRef
        const mapContainer = useRef<HTMLDivElement | null>(null);
    
        useEffect(() => {
            // mapContainer.currentはnullになり得るので型ガード(ていねい)
            if (!mapContainer.current) return;
    
            const map = new mapboxgl.Map({
                container: mapContainer.current, // ていねいな型ガードのおかげで必ずHTMLDivElementとして扱える、current!でも可
                style: mapStyle,
                center: [142.0, 40.0],
                zoom: 4,
            });
            // mapboxgl.Mapのインスタンスへの参照を保存
            setMapInstance(map);
        }, []);
        return <div style={{ height: 800 }} ref={mapContainer} />;
    };
    export default Map;
    
    
    

    무사히 지도 화면이 표시되었습니다.



    TIPS: 지도의 레이어 구성 조작에 대해



    ※이하의 구성은 상기의 코드 약간 다릅니다만, 말하고 싶은 것에 변함은 없으므로 적절히 읽어 주세요

    실제 지도 애플리케이션 개발에서는 지도의 레이어 구성을 동적으로 업데이트하고 싶을 수 있습니다. Mapbox GL JS에서는, 그 때, addSource() 하고, addLayer() 하고, 하지만 그 때 source가 이미 존재하고 있으면… 그래서 이러한 API는 일절 사용하지 않고, Style 자체를 조작하는 방법을 추천합니다. 다음이 구현 예.
    import React, { useEffect, useState, useRef } from 'react';
    import styled from 'styled-components';
    
    import mapboxgl from 'mapbox-gl';
    import 'mapbox-gl/dist/mapbox-gl.css';
    
    const MapDiv = styled.div`
        height: 800px;
    `;
    
    // Mapbox Style
    const initMapStyle: mapboxgl.Style = {
        version: 8,
        sources: {
            OSM: {
                type: 'raster',
                tiles: ['http://tile.openstreetmap.org/{z}/{x}/{y}.png'],
                tileSize: 256,
                attribution:
                    '<a href="http://osm.org/copyright">© OpenStreetMap contributors</a>',
            },
        },
        layers: [
            {
                id: 'OSM',
                type: 'raster',
                source: 'OSM',
                minzoom: 0,
                maxzoom: 18,
            },
        ],
    };
    
    const emptyMapStyle: mapboxgl.Style = {
        version: 8,
        sources: {},
        layers: [],
    };
    
    const Map: React.FC = () => {
        // mapboxgl.Mapのインスタンスへの参照を保存するためのuseState
        const [mapInstance, setMapInstance] = useState<mapboxgl.Map>();
    
        // 地図表示するDiv要素をHTML要素を特定するためのuseRef
        const mapContainer = useRef<HTMLDivElement | null>(null);
    
        // 地図スタイルをstate管理
        const [mapStyle, setMapStyle] = useState<mapboxgl.Style>(initMapStyle);
        const [flag, setFlag] = useState(false);
    
        // mapStyleの変更時に走る処理
        useEffect(() => {
            if (!mapInstance) {
                // nullチェック
                return;
            }
            // MapインスタンスのsetStyle()を実行
            mapInstance.setStyle(mapStyle);
        }, [mapStyle]);
    
        useEffect(() => {
            // 初回時のみ走る処理
            if (!mapInstance) {
                if (!mapContainer.current) {
                    // mapContainer.currentはnullになり得るので型ガード
                    return;
                }
                const map = new mapboxgl.Map({
                    container: mapContainer.current, // 型ガードのおかげで必ずHTMLDivElementとして扱える
                    style: mapStyle,
                    center: [142.0, 40.0],
                    zoom: 4,
                });
    
                // mapboxgl.Mapのインスタンスへの参照を保存
                setMapInstance(map);
            }
        }, [mapInstance]);
    
        return (
            <MapDiv
                ref={mapContainer}
                onClick={() => {
                    // 以下のようにReactのstateを操作するとMapインスタンス側でsetStyle()が走る
                    setMapStyle(flag ? emptyMapStyle : initMapStyle);
                    setFlag(!flag);
                }}
            />
        );
    };
    
    export default Map;
    

    간단하게 OSM 스타일과 하늘 스타일을 오가는 샘플입니다만, React측의 변수의 조작만으로 Map 인스턴스의 setStyle()를 발화시킬 수가 있습니다. 데이터의 흐름도 매우 간단합니다 (React의 스타일 변수의 변경으로부터, 항상 한 방향으로 Map 인스턴스에 반영된다). 만약 여기저기에서 addSource()나 addLayer()를 반복하면(자), Style의 관리가 매우 복잡하게 됩니다(행수도 증가해 버려 아무것도 좋은 일이 없습니다).
    이것은 라이브러리 자체의 할 수 있다고 생각합니다만, setStyle()는 스타일 전부를 재렌더링하는 일은 없고, 변경 부분만을 다시 그려 주는 것 같기 때문에 퍼포먼스에의 영향은 없습니다(아마).

    좋은 웹페이지 즐겨찾기