LoginSignup
7
10

More than 1 year has passed since last update.

Mapbox GL JSをReactの関数コンポーネントで表示する改

Last updated at Posted at 2021-01-02

はじめに

以前に投稿した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 mapbox-gl@v1.13.0 @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;


無事に地図画面が表示されました。

スクリーンショット 2021-01-03 7.34.41.png

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()はスタイル全てを再レンダリングする事はなく、変更箇所のみを改めて描画してくれるようなのでパフォーマンスへの影響はないです(たぶん)。

7
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
10