Vue.js 공식 예제에도 나와있는 부분입니다.
https://vuejs.org/v2/cookbook/practical-use-of-scoped-slots.html#ad
1. 개발환경 구성
간단히 vue/cli를 이용해 구성합니다.
> npm install -g @vue/cli
> vue create project-name
>> Manually select features
>> check Babel, Linter/Formatter
>> ESLint + Standard config
>> Lint on save
>> In dedicated config files
>> N
(아마도 전부 기본설정..)
> cd project-name
> npm run serve
http://localhost:8080/ 에 접속하시면 Welcome to Your Vue.js App 화면이 나옵니다.
디렉토리 구조는 다음과 같습니다.
project-name
ㄴpublic
ㄴsrc
ㄴㄴassets
ㄴㄴcomponents
ㄴㄴApp.vue
ㄴㄴmain.js
2. google maps api loader 구현
project-name/src/js/googleMapsApiLoader.js
let googleApi
const callback = 'googleMapsApiLoadCallback'
function googleMapsApiLoader (key) {
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = `https://maps.googleapis.com/maps/api/js?key=${key}&callback=${callback}`
const head = document.querySelector('head')
head.appendChild(script)
}
function load (key) {
if (googleApi) return Promise.resolve(googleApi)
return new Promise((resolve, reject) => {
googleMapsApiLoader(key)
window[callback] = function () {
googleApi = window.google
resolve(googleApi)
}
setTimeout(() => {
if (!window.google) reject(new Error('Google maps api load failed'))
}, 5000)
})
}
export default load
npm 에 등록되어있는 googleMapsApiLoader의 코드입니다. 필요한 부분만 간단하게 발췌했습니다.
3. googleMap Higher-order component 작성
project-name/src/components/map.js
import googleMapsApiLoader from '@/js/googleMapsApiLoader' export default (key) => { return { render (h) { const el = 'div' const options = { ref: 'map' } return h(el, options) }, data () { return { google: null, map: null } }, mounted () { googleMapsApiLoader(key) .then((google) => { this.google = google this.drawMap() }) }, methods: { drawMap () { const el = this.$refs.map const map = new this.google.maps.Map(el, { center: { lat: 37, lng: 127 }, zoom: 14 }) this.map = map } } } }
api 키를 받아 api 를 로드하고 맵을 그리는 컴포넌트 입니다.
4. map component 를 전역으로 등록해주는 플러그인 작성
project-name/src/plugins/vue-google-map-wrapper.js
import googleMapFactory from '@/components/map' function install (Vue, options) { const { apiKey } = options const googleMap = googleMapFactory(apiKey) Vue.component('googleMap', googleMap) } export default install
5. Vue.use(plugin) 으로 google map component 전역 등록
google maps api 키는 발급받으셔야합니다.
project-name/src/main.js
import Vue from 'vue' import App from './App.vue' import vueGoogleMapWrapper from '@/plugins/vue-google-map-wrapper' Vue.config.productionTip = false Vue.use(vueGoogleMapWrapper, { apiKey: 'YOUR_API_KEY' }) new Vue({ render: h => h(App) }).$mount('#app')
6. app.vue 를 편집해서 확인해봅시다.
project-name/src/app.vue
<template> <div id="app"> <google-map class="googleMap"></google-map> </div> </template> <script> export default { name: 'app' } </script> <style> .googleMap { height: 500px } </style>
http://localhost:8080 에 접속하면 구글맵이 떠있습니다.
7. scopedSlot 으로 google과 map 노출
맵에 마커등을 찍기위해서는 window.google 과 google.maps.Map 이 필요합니다.
둘다 방금만든 구글맵 컴포넌트의 data에 등록되어있습니다.
구글맵 컴포넌트에 scopedSlot 을 생성해 google과 map을 노출하여 자식 컴포넌트가 사용할 수 있도록 만듭니다.
project-name/src/components/map.js
// ... render (h) { const el = 'div' const options = { ref: 'map' } if (this.google && this.map && Object.keys(this.$scopedSlots).length) { const scopedSlot = this.$scopedSlots.default({ google: this.google, map: this.map }) return h(el, options, [scopedSlot]) } return h(el, options) }, // ...
project-name/src/app.js
// ... <google-map class="googleMap"> <div slot-scope="{ google, map }"></div> </google-map> // ...
앞으로 이런식으로 구글맵 컴포넌트의 google과 map 에 접근할 수 있습니다.
8. marker wrapper 컴포넌트 작성
구글맵 컴포넌트와 동일한 방식으로 만듭니다.
project-name/src/components/marker.js
export default { props: { google: { type: Object, required: true }, map: { type: Object, required: true }, position: { type: Object, required: true } }, render (h) { return h() }, mounted () { this.drawMarker() }, destroyed () { this.clearMarker() }, data () { return { marker: null } }, methods: { drawMarker () { const marker = new this.google.maps.Marker({ map: this.map, position: this.position }) this.marker = marker }, clearMarker () { this.marker.setMap(null) } } }
google, map, position 을 props로 받고, 마운트 될때 해당 맵에 마커를 그립니다.
이 컴포넌트가 사라진다고해서 맵에 마커가 사라지는것은 아니기때문에, destroyed hook에 marker.setMap(null)을 걸어줍니다.
플러그인에도 등록합니다.
project-name/src/plugins/vue-google-map-wrapper.js
import googleMapFactory from '@/components/map' import googleMapMarker from '@/components/marker' function install (Vue, options) { const { apiKey } = options const googleMap = googleMapFactory(apiKey) const components = { googleMap, googleMapMarker } Object.entries(components).forEach(([componentName, component]) => { Vue.component(componentName, component) }) } export default install
9. 마커 찍어보기
project-name/src/app.vue
<template> <div id="app"> <google-map class="googleMap"> <div slot-scope="{ google, map }"> <google-map-marker :google="google" :map="map" :position="position"> </google-map-marker> </div> </google-map> </div> </template> <script> export default { name: 'app', data () { return { position: { lat: 37, lng: 127 } } } } </script> <style> .googleMap { height: 500px } </style>
10. 이벤트 리스닝
맵과 마커에 이벤트 리스너를 등록하고 활용해봅니다.
project-name/src/components/map.js
// ... methods: { drawMap () { const el = this.$refs.map const map = new this.google.maps.Map(el, { center: { lat: 37, lng: 127 }, zoom: 14 }) map.addListener('click', (e) => { this.$emit('click', e) }) this.map = map } } // ...
project-name/src/components/marker.js
// ... methods: { drawMarker () { const marker = new this.google.maps.Marker({ map: this.map, position: this.position }) marker.addListener('click', (e) => { this.$emit('click', e) }) this.marker = marker }, clearMarker () { this.marker.setMap(null) } } // ...
project-name/src/app.vue
<template> <div id="app"> <google-map class="googleMap" @click="addMarker"> <div slot-scope="{ google, map }"> <google-map-marker v-for="(marker, index) in markers" :google="google" :map="map" :key="index" :position="marker.position" @click="panTo($event, map)" > </google-map-marker> </div> </google-map> </div> </template> <script> export default { name: 'app', data () { return { markers: [ { position: { lat: 37, lng: 126.98 } }, { position: { lat: 37, lng: 126.99 } }, { position: { lat: 37, lng: 127.00 } }, { position: { lat: 37, lng: 127.01 } }, { position: { lat: 37, lng: 127.02 } } ] } }, methods: { addMarker (e) { const { lat, lng } = e.latLng.toJSON() this.markers.push({ position: { lat, lng } }) }, panTo (e, map) { map.panTo(e.latLng) } } } </script> <style> .googleMap { height: 500px } </style>
11. googleMaps custom control 컴포넌트 작성
맵에 custom control 을 쉽게 추가할 수 있는 컴포넌트를 작성해봅니다.
project-name/src/components/custom-control.js
const name = 'customControl' export default { props: { google: { type: Object, required: true }, map: { type: Object, required: true }, position: { type: String, default: 'LEFT_TOP' } }, render (h) { const el = 'div' const options = { ref: name } const slot = this.$slots.default[0] return h(el, options, [slot]) }, mounted () { this.addControl() }, methods: { addControl () { const el = this.$refs[name] this.map.controls[this.google.maps.ControlPosition[this.position]].push(el) } } }
플러그인에도 등록합니다.
project-name/src/plugins/vue-google-map-wrapper.js
import googleMapFactory from '@/components/map' import googleMapMarker from '@/components/marker' import googleMapCustomControl from '@/components/custom-control' function install (Vue, options) { const { apiKey } = options const googleMap = googleMapFactory(apiKey) const components = { googleMap, googleMapMarker, googleMapCustomControl } Object.entries(components).forEach(([componentName, component]) => { Vue.component(componentName, component) }) } export default install
사용은 다음과같이 합니다.
project-name/src/app.vue
<template> <div id="app"> <google-map class="googleMap" @click="addMarker"> <div slot-scope="{ google, map }"> <google-map-marker v-for="(marker, index) in markers" :google="google" :map="map" :key="index" :position="marker.position" @click="panTo($event, map)" > </google-map-marker> <google-map-custom-control :google="google" :map="map" position="BOTTOM_CENTER"> <input type="text"> </google-map-custom-control> </div> </google-map> </div> </template> // ...
github: https://github.com/bongjinpark1/vue-google-maps-wrapper
궁금증이나 더 좋은 패턴등 댓글달아주세요.