Close
升级到 Vue 3 | Vue 2 EOL

使用作用域插槽与 Google Maps 的实际应用

基本示例

在某些情况下,您可能希望插槽内的模板能够访问负责渲染插槽内容的子组件的数据。这在您需要自由创建使用子组件数据属性的自定义模板时特别有用。这是作用域插槽的典型用例。

想象一个组件,它配置并准备一个外部 API 以供另一个组件使用,但没有与任何特定模板紧密耦合。然后,这样的组件可以在多个地方重复使用,渲染不同的模板,但使用相同的基对象和特定的 API。

我们将创建一个组件 (GoogleMapLoader.vue),它

  1. 初始化 Google Maps API
  2. 创建 googlemap 对象
  3. 将这些对象暴露给使用 GoogleMapLoader 的父组件

以下是如何实现此目的的示例。我们将在下一节逐段分析代码,并了解实际发生的事情。

首先,让我们建立我们的 GoogleMapLoader.vue 模板

<template>
<div>
<div class="google-map" ref="googleMap"></div>
<template v-if="Boolean(this.google) && Boolean(this.map)">
<slot
:google="google"
:map="map"
/>
</template>
</div>
</template>

现在,我们的脚本需要传递一些 props 到组件,这使我们能够设置 Google Maps API地图对象

import GoogleMapsApiLoader from 'google-maps-api-loader'

export default {
props: {
mapConfig: Object,
apiKey: String,
},

data() {
return {
google: null,
map: null
}
},

async mounted() {
const googleMapApi = await GoogleMapsApiLoader({
apiKey: this.apiKey
})
this.google = googleMapApi
this.initializeMap()
},

methods: {
initializeMap() {
const mapContainer = this.$refs.googleMap
this.map = new this.google.maps.Map(
mapContainer, this.mapConfig
)
}
}
}

这只是工作示例的一部分,您可以在下面的 Codesandbox 中找到完整的示例。

实际示例:创建 Google 地图加载器组件

1. 创建一个初始化我们地图的组件

GoogleMapLoader.vue

在模板中,我们为地图创建一个容器,该容器将用于挂载从 Google Maps API 中提取的 地图 对象。

<template>
<div>
<div class="google-map" ref="googleMap"></div>
</div>
</template>

接下来,我们的脚本需要从父组件接收 props,这将使我们能够设置 Google 地图。这些 props 包括

import GoogleMapsApiLoader from 'google-maps-api-loader'

export default {
props: {
mapConfig: Object,
apiKey: String,
},

然后,我们将 google 和 map 的初始值设置为 null

data() {
return {
google: null,
map: null
}
},

mounted 钩子中,我们从 GoogleMapsApi 实例化 googleMapApiMap 对象,并将 googlemap 的值设置为创建的实例

  async mounted() {
const googleMapApi = await GoogleMapsApiLoader({
apiKey: this.apiKey
})
this.google = googleMapApi
this.initializeMap()
},

methods: {
initializeMap() {
const mapContainer = this.$refs.googleMap
this.map = new this.google.maps.Map(mapContainer, this.mapConfig)
}
}
}

到目前为止,一切都很好。完成所有这些操作后,我们可以继续将其他对象添加到地图(标记、折线等)并将其用作普通地图组件。

但是,我们希望将 GoogleMapLoader 组件仅用作准备地图的加载器 — 我们不想在其上渲染任何内容。

为了实现这一点,我们需要允许将使用 GoogleMapLoader 的父组件访问 this.googlethis.map,这些属性是在 GoogleMapLoader 组件内部设置的。这就是 作用域插槽 真正发挥作用的地方。作用域插槽允许我们将子组件中设置的属性暴露给父组件。这听起来可能像盗梦空间,但请再耐心等待一分钟,我们将进一步分解它。

2. 创建使用我们的初始化组件的组件。

TravelMap.vue

在模板中,我们渲染 GoogleMapLoader 组件并传递初始化地图所需的 props。

<template>
<GoogleMapLoader
:mapConfig="mapConfig"
apiKey="yourApiKey"
/>
</template>

我们的脚本标签将如下所示

<script>
import GoogleMapLoader from './GoogleMapLoader'
import { mapSettings } from '@/constants/mapSettings'

export default {
components: {
GoogleMapLoader
},

computed: {
mapConfig () {
return {
...mapSettings,
center: { lat: 0, lng: 0 }
}
},
},
}
</script>

仍然没有作用域插槽,所以让我们添加一个。

3. 通过添加作用域插槽将 googlemap 属性暴露给父组件。

最后,我们可以添加一个作用域插槽,它将完成这项工作,并允许我们在父组件中访问子组件 props。我们通过在子组件中添加 <slot> 标签并传递我们想要暴露的 props(使用 v-bind 指令或 :propName 简写)来做到这一点。它与将 props 传递给子组件没有什么不同,但在 <slot> 标签中执行此操作将反转数据流的方向。

GoogleMapLoader.vue

<template>
<div>
<div class="google-map" ref="googleMap"></div>
<template v-if="Boolean(this.google) && Boolean(this.map)">
<slot
:google="google"
:map="map"
/>
</template>
</div>
</template>

现在,当我们在子组件中拥有插槽时,我们需要在父组件中接收和使用暴露的 props。

4. 使用 slot-scope 属性在父组件中接收暴露的 props。

为了在父组件中接收 props,我们声明一个模板元素并使用 slot-scope 属性。此属性可以访问承载从子组件暴露的所有 props 的对象。我们可以获取整个对象,也可以 解构该对象 并仅获取我们需要的部分。

让我们解构这个东西来获取我们需要的部分。

TravelMap.vue

<GoogleMapLoader
:mapConfig="mapConfig"
apiKey="yourApiKey"
>
<template slot-scope="{ google, map }">
{{ map }}
{{ google }}
</template>
</GoogleMapLoader>

即使 googlemap props 不存在于 TravelMap 范围内,该组件也可以访问它们,我们可以在模板中使用它们。

您可能想知道为什么我们要这样做,以及这样做有什么用?

作用域插槽允许我们向插槽传递模板而不是渲染的元素。它被称为 作用域 插槽,因为它即使在父组件范围内渲染模板,也能够访问某些子组件数据。这使我们能够自由地用父组件的自定义内容填充模板。

5. 为标记和折线创建工厂组件

现在,当我们的地图准备就绪时,我们将创建两个工厂组件,它们将用于向 TravelMap 添加元素。

GoogleMapMarker.vue

import { POINT_MARKER_ICON_CONFIG } from '@/constants/mapSettings'

export default {
props: {
google: {
type: Object,
required: true
},
map: {
type: Object,
required: true
},
marker: {
type: Object,
required: true
}
},

mounted() {
new this.google.maps.Marker({
position: this.marker.position,
marker: this.marker,
map: this.map,
icon: POINT_MARKER_ICON_CONFIG
})
}
}

GoogleMapLine.vue

import { LINE_PATH_CONFIG } from '@/constants/mapSettings'

export default {
props: {
google: {
type: Object,
required: true
},
map: {
type: Object,
required: true
},
path: {
type: Array,
required: true
}
},

mounted() {
new this.google.maps.Polyline({
path: this.path,
map: this.map,
...LINE_PATH_CONFIG
})
}
}

这两个组件都接收 google,我们使用它来提取所需的对象(标记或折线),以及 map,它为我们提供了要放置元素的地图的引用。

每个组件还期望一个额外的 prop 来创建相应的元素。在本例中,我们分别有 markerpath

mounted 钩子中,我们创建一个元素(标记/折线)并通过将 map 属性传递给对象构造函数将其附加到我们的地图。

还有一步要走…

6. 向地图添加元素

让我们使用我们的工厂组件向地图添加元素。我们必须渲染工厂组件并传递 googlemap 对象,以便数据流向正确的位置。

我们还需要提供元素本身所需的 data。在本例中,它是带有标记位置的 marker 对象和带有折线坐标的 path 对象。

就是这样,将数据点直接集成到模板中

<GoogleMapLoader
:mapConfig="mapConfig"
apiKey="yourApiKey"
>
<template slot-scope="{ google, map }">
<GoogleMapMarker
v-for="marker in markers"
:key="marker.id"
:marker="marker"
:google="google"
:map="map"
/>
<GoogleMapLine
v-for="line in lines"
:key="line.id"
:path.sync="line.path"
:google="google"
:map="map"
/>
</template>
</GoogleMapLoader>

我们需要在我们的脚本中导入所需的工厂组件,并设置将传递给标记和折线的 data

import { mapSettings } from '@/constants/mapSettings'

export default {
components: {
GoogleMapLoader,
GoogleMapMarker,
GoogleMapLine
},

data () {
return {
markers: [
{ id: 'a', position: { lat: 3, lng: 101 } },
{ id: 'b', position: { lat: 5, lng: 99 } },
{ id: 'c', position: { lat: 6, lng: 97 } },
],
lines: [
{ id: '1', path: [{ lat: 3, lng: 101 }, { lat: 5, lng: 99 }] },
{ id: '2', path: [{ lat: 5, lng: 99 }, { lat: 6, lng: 97 }] }
],
}
},

computed: {
mapConfig () {
return {
...mapSettings,
center: this.mapCenter
}
},

mapCenter () {
return this.markers[1].position
}
},
}

何时避免这种模式

根据示例创建非常复杂的解决方案可能很诱人,但在某些时候,我们可能会遇到这种抽象成为代码库中独立部分的情况。如果我们遇到了这种情况,可能值得考虑将其提取到一个附加组件中。

总结

就是这样。通过创建所有这些部分,我们现在可以通过向每个地图传递不同的模板来重复使用 GoogleMapLoader 组件作为所有地图的基础。想象一下,您需要创建另一个具有不同标记或仅具有标记而没有折线的地图。通过使用上述模式,这变得非常容易,因为我们只需要向 GoogleMapLoader 组件传递不同的内容。

这种模式并不严格与 Google 地图相关联;它可以与任何库一起使用,以设置基本组件并暴露库的 API,然后可以在调用基本组件的组件中使用该 API。