Help us understand the problem. What is going on with this article?

Vue.jsでライブラリ使わずにgoogle mapを利用する

More than 1 year has passed since last update.

vuejsではvue2-google-mapなどgoogle-mapを利用するライブラリがあるが、色々小回りを効かせたいくて直接APIを触れるようにしたかったのでやってみたらvueのあらゆる機能を触ることになったので実装をまとめてみる。

出来たもの

Demo: https://vue-google-map-provider-sample.netlify.com/
Source: https://github.com/inuscript/example-vue-inject-provide-google-map

実装

登場人物は下記のようになる

  • index.html (起動するファイル)
  • MyMap.vue (マップの大本の実装。コンポーネントを組み合わせる親)
  • MapLoader.vue (Google Mapを呼び出すだけ)
  • MapProvider.vue (vueのprovideを提供するだけ)
  • ChildMarker.vue (例として子要素でマーカーを表示する)

google-map以外でもcallbackを利用するような場合に扱える知見を得ることが出来たので、ここから実装について書いてみる。

index.html

    <!-- index.html -->
    <div id="app">
      <my-map :markers='[
        {"lat":35.6432027,"lng":139.6729435},
        {"lat":35.5279833,"lng":139.6989209},
        {"lat":35.6563623,"lng":139.7215211},
        {"lat":35.6167531,"lng":139.5469376},
        {"lat":35.6950961,"lng":139.5037899}
      ]'>
      </my-map>
    </div>

index.htmlは呼び出すだけなので何も無し

MyMap.vue

呼び出しの中核はこんな感じにしてみた。
ここは汎用的に使えないものとして書いている。

<!-- MyMap.vue -->
<template>
  <div>
    <h1>Map</h1>
    <map-loader 
      :map-config="mapConfig"
      apiKey="YOUR API KEY"
    >
      <template v-for="marker in markers">
        <child-marker :position="marker" />
      </template>
    </map-loader>
  </div>
</template>

<script>
import MapLoader from "./MapLoader.vue"
import ChildMarker from './ChildMarker'

export default {
  props: {
    markers: Array
  },
  data(){
    return {
      mapConfig: {
        zoom: 12,
        center: this.markers[0]
      }
    }
  },
  components: {
    MapLoader,
    ChildMarker
  }
}
</script>

ここからmap-loaderを呼び出し、内部でchild-markerを利用している

MapLoader

肝の部分その1。google mapをロードを担う

<!-- MapLoader.vue -->
<template>
  <div>
    <div id="map"></div> <!-- point 1 -->
    <template v-if="!!this.google && !!this.map"> <!-- point 2 -->
      <map-provider
        :google="google"
        :map="map"
      >
        <slot/>
      </map-provider>
    </template>
  </div>
</template>

<script>
import GoogleMapsApiLoader from 'google-maps-api-loader'
import MapProvider from './MapProvider'

export default {
  props:{
    mapConfig: Object,
    apiKey: String
  },
  components: {
    MapProvider
  },
  data(){
    return {
      google: null,
      map: null
    }
  },
  mounted () { // point 3
    GoogleMapsApiLoader({
      apiKey: this.apiKey
    }).then((google) => {
      this.google = google
      this.initializeMap()
    })
  },
  methods: {
    initializeMap (){
      const mapContainer = this.$el.querySelector('#map') // point 1
      const { Map } = this.google.maps
      this.map = new Map(mapContainer, this.mapConfig)
    }
  }
}
</script>

<style scoped>
#map {
  height: 100vh;
  width: 100%;
}
</style>
  • point 1: #map google mapを吐き出す部分としてdomを使っている。それを後半でthis.$elを利用してマウントしている。
  • point 2: 一番のキモ。googlemapがロード完了するまで子を レンダリングさせない。この制御をすることで、provide/injectをうまく利用出来る。
  • point 3: ロードの処理どのタイミングでやるの?というのはmountedで行う。ここでGoogleMapApiLoader

MapProvider

キモその2。
vueのprovide/injectを利用するために、loaderから分離した。
provideはコンポーネントがロードされた初期のタイミングでのみ評価されるらしく、loaderで値の算出が完了した後に実行されるようにしないといけない。
loaderがgooglemapが揃うまで子要素をレンダリングしないようにすることで、MapProviderは確実にgooglemapの値を取得出来る状態になっている。

<!-- MapProvider.vue -->
<template>
  <div>
    <slot />
  </div>
</template>

<script>
export default {
  props: {
    google: Object,
    map: Object,
  },
  provide() {
    return {
      google: this.google,
      map: this.map
    }
  },
}
</script>

ChildMarker

<!-- ChildMarker.vue -->
<template></template>
<script>
export default {
  inject: ["google", "map"],
  props: {
    position: Object
  },
  data(){
    return { marker: null}
  },
  mounted(){
    const { Marker } = this.google.maps
    this.marker = new Marker({
      position: this.position,
      map: this.map,
      title: "Child marker!"
    })
  }
}
</script>

子要素の実装側。
injectを利用してprovideされた値を貰ってくる。
MapLoaderでやったのと同じく、mountedで処理をする。表示は何もしなくて良いので空にしている。

provide/injectを使わないパターン(slot-scopeを使う)

provide / injectは公式ドキュメントによれば「ライブラリ向けで、一般ユーザーは使うべきでない」という話もある。
これらを使わない場合は、値をバケツリレーして子要素に渡していくことになる。
ここでvueの場合はslot-socpeを利用する必要が出てくる

MapLoaderはMapProvierを利用せず、slotに直接googlemapの値を渡す

<!-- MapLoader.vue -->
<template>
  <div>
    <div id="map"></div>
    <template v-if="!!this.google && !!this.map">
      <slot
        :google="google"
        :map="map"
      />
    </template>
  </div>
</template>

<script>
 :
</script>

そしてMyMap側でslot-scopeを利用して、map-loaderから受け取ったgooglemapの変数をchild-markerへ渡す

<!-- MyMap -->
<template>
  <div>
    <h1>Map</h1>
    <map-loader 
      :map-config="mapConfig"
      apiKey="YOUR API KEY">
      <template slot-scope="scopeProps"> <!-- slot-scope -->
        <child-marker 
          v-for="(marker,i) in markers"
          :key="i"
          :position="marker" 
          :google="scopeProps.google"
          :map="scopeProps.map"
        />
      </template>
    </map-loader>
  </div>
</template>

ChildMarkerはinjectがなくなりpropsで値を受けるようになる

<!-- ChildMarker -->
<template></template>
<script>
export default {
  props: {
    google: Object,
    map: Object,
    position: Object
  },
  data(){
    return { marker: null }
  },
  mounted(){
    const { Marker } = this.google.maps
    this.marker = new Marker({
      position: this.position,
      map: this.map,
      title: "Child marker!"
    })
  }
}
</script>
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした