11x6 精读Vue官方文档 - CookBook - 在 GoogleMaps 中实际使用作用域插槽

457 阅读2分钟

精读 Vue 官方文档系列 🎉


Render Less

当你存在某个组件需要使用另一个组件内部的数据,但又不想把该组件作为另一个组件的子组件紧密的方式来耦合使用,那么 render less 将会是一个非常好的选择。

render less 依赖于 scopeSlot 技术,它可以让插槽内的模板能够访问来自负责呈现插槽内容的组件的数据,利用这一技术特性,我们可以创建类似于可插拔的组件概念,而无需在物理结构上将一个组件内置到另一个组件内部,作为子组件来使用。

作用域插槽允许我们将子组件中设置的属性公开给父组件。

首先,我们要将“负责呈现插槽内容的组件(提供数据的组件)”视作为一个“基座组件”。然后将需要消费基座组件提供数据的组件使用 slot 进行插拔。

简单的示例

一个提供常用正则表达式与校验方法的基座组件

<script>
const regexps = {
  email:
    /^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
};
const validators = {
  validateEmail(value) {
    return regexps.email.test(value);
  },
};
export default {
  name: "RegExp",
  render(c) {
    return c("span", [
      this.$scopedSlots.default({
        regexps,
        ...validators
      }),
    ]);
  },
};
</script>

下面,开始通过“插槽”的方式来实践 render less 的思想,

<template>
  <div id="app">
    <reg-exp v-slot="{ validateEmail }">
      <input @change="validate($event, validateEmail)" />
    </reg-exp>
  </div>
</template>

<script >
import Vue from "vue";
export default Vue.extend({
  name: "App",
  methods: {
    validate(e, callback) {
      console.log(callback(e.target.value));
    },
  },
});
</script>

GoogleMaps 示例

相同的步骤,我们来定义 Render Less基座组件 ——— GoogleMaps

GoogleMaps 接收 props 传递的初始化参数,用来创建 GoogleMap 的实例。然后再通过 scopeSlots 将实例公开给其父组件。

<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>
<script>
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
      )
    }
  }
}
</script>

接着我们来定义消费基座组件的插槽组件。

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 props 可以用来提取所需对象(标记或折线)以及作为 map 对我们想要放置元素的地图的引用。

每个组件还需要一个额外的道具来创建相应的元素。在这种情况下,我们分别有 markerpath

在挂载的钩子上,我们创建一个元素(Marker/Polyline)并通过将 map 属性传递给对象构造函数将其附加到我们的地图上。

最后,便是我们完整的应用:

<template>
<GoogleMaps
  :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>
</GoogleMaps>
</template>
<script>
import { mapSettings } from '@/constants/mapSettings'

export default {
  components: {
    GoogleMaps,
    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
    }
  },
}
</script>