# 创建插件组件
# 概述
Webclient-Vue-Mapboxgl是针对原始的mapbox-gl脚本的封装。很多额外的高效的插件也是项目开发中常常使用的。因此为了将常用的mapbox-gl的插件也通过Vue的方式使用,下面将介绍如何创建插件组件。
一个插件组件本质上还是通过Vue组件的方式,核心思想是通过获取 根组件mapbox-map
中的 mapbox
and map
对象来进行对应的拓展功能。
Webclient-Vue-Mapboxgl内部使用独立的VUE注入机制provide/inject (opens new window)。
当mapbox-map
组件创建的时候,必须等到地图初始化加载完毕后,然后才会渲染子组件 同时通过provide的方式提供下面三个组件:
TIP
mapbox
(Mapbox GL官方脚本),map
(Map实例 (opens new window))actions
(promisified 封装的同步的地图方法).
只要是注入了上面的属性,你就可以添加地图要素或者执行对应的地图方法。 注入方法如下:
inject: ["mapbox", "map", "actions"],
封装插件的核心思想是: 保持Vue本身的声明规范,这样做可以容易封装额外的控制组件和图层组件.同时也容易封装第三方插件,适应不同的业务需求。
这里我在 左侧导航栏
源码分析
-注记视图
里面有详细的描述,请认真阅读。
# 基本示例
App项目代码
<template>
<mapbox-map :accessToken="accessToken" :mapStyle="mapStyle">
<MyPluginComponent />
</mapbox-map>
</template>
<script>
import Mapbox from "@mapgis/mapbox-gl";
import { MapgisWebMap } from "@mapgis/webclient-vue-mapboxgl";
export default {
components: {
MapgisWebMap
},
data() {
return {
accessToken: ACCESS_TOKEN,
mapStyle: MAP_STYLE
};
}
};
</script>
自定义插件组件
<template>
<button @click="fly">Fly!</button>
<div>地图中心是: 经度={{ center.lng }}, 纬度={{ center.lat }}</div>
</template>
<script>
import Mapbox from "@mapgis/mapbox-gl";
// import { MapgisWebMap } from "@mapgis/webclient-vue-mapboxgl";
export default {
name: "MyPluginComponent"
inject: ["mapbox", "map", "actions"],
data() {
return {
center: null
};
},
created() {
this.center = this.map.getCenter();
},
methods: {
async fly() {
const flyResult = await this.actions.flyTo({ center: [10, 10] });
this.center = flyResult.center;
}
}
};
</script>
# 实验性功能 helpers experimental
Experimental
helpers是一个实验性的功能,类似数据库,我们往往在操作数据库的时候很多情况都是通过一个DBHelper、DBLink的方式去连接数据库,因为常见很多SQL语法完全可以封装起来。 这里提出的helpers就是这个目的。
这个试验特性在后续的版本中会不断的改变,我们会尽可能保证长期的向后兼容。这个特性会一直给个experimental
的提示。
目前他们只是 把Webclient-Vue-Mapboxgl
框架中的事件机制暴露出去了。
除了基本的地图对象,helpers还提供了一些很有用的注入,这些特性都会包含在$helpers中。代码如下:
import { $helpers } from "@mapgis/webclient-vue-mapboxgl";
const { withEvents, withSelfEvents, asControl, asLayer } = $helpers;
# withEvents
Source (opens new window).
提供了$_emitEvent
和 $_emitMapEvent
两种方法发送 Vue风格的事件
$_emitEvent
着重强调发送Vue自身特性的事件可以自定义$_emitMapEvent
着重强调发送map自身的事件,一般和地图事件同名
# withSelfEvents
Source (opens new window)
$_bindSelfEvents
, $_unbindSelfEvents
and $_emitSelfEvent
.
上面三者的核心思想其实就是一个,就是把controls, markers and popups绑定的Dom-Html元素添加对应的事件监听并把该事件传递给Vue组件。
通俗的说就是 将地图内部的html的事件对外暴露给Vue组件。 也可以理解为将地图对象的内部事件从map映射到Vue组件上。
# asControl
Source (opens new window).
提供地图控制视图的主代码结构, 核心是封装了 map.addControl / removeControl(this.control);
# asLayer
Source (opens new window).
提供地图图层的主代码结构 layers API
# 从原生插件创建组件
举例说明如何从一个原生mapbox插件创建Vue组件。
VueMaboxGeocoder (opens new window) — 包装 mapbox-gl-geocoder (opens new window):
// 因为该组件没有HTML需要渲染,因此不需要<template>,只用js就可以了。
import MapboxGeocoder from "@mapbox/mapbox-gl-geocoder";
import { $helpers } from "@mapgis/webclient-vue-mapboxgl"; //获取 $helpers
// 定义mapbox-gl-geocoder事件
const geocoderEvents = {
clear: "clear",
loading: "loading",
results: "results",
result: "result",
error: "error"
};
export default {
name: "GeocoderControl",
mixins: [$helpers.asControl], // 因为是控制组件,所以需要注入asControl
inject: ["mapbox", "map"], // 获取根组件传递来的对象
props: {
//该插件需要秘钥
accessToken: {
type: String,
required: true
},
input: {
type: String,
default: null
},
proximity: {
type: Object,
default: null
}
},
data() {
return {
initial: true
};
},
// 观察必要的属性
watch: {
input: {
handler(next, prev) {
if (this.control && next !== prev) {
this.control.setInput(next);
}
},
immediate: true
},
proximity(next, prev) {
if (this.control && next !== prev) {
this.control.setProximity(next);
}
}
},
created() {
// 特别注意这里的this.control不是使用的 data里面的而是 组件自身的this
// 核心原因在于 如果使用data或者vuex会使得和Vue自身的响应式机制混乱
// 因此使用一种非响应式的属性来记录,类似全局变量的形式
this.control = null;
if (this.accessToken && !this.mapbox.accessToken) {
this.mapbox.accessToken = this.accessToken;
}
this.control = new MapboxGeocoder(this.$props); // 创建对象
this.control.on("results", this.$_updateInput); // 当用户输入的时候,更新同步属性 "input"
// 把组件添加到地图上
this.$_deferredMount();
},
beforeDestroy() {
this.control.off("results", this.$_updateInput);
// 由于是注入了asControl,因此控制组件会在asControl的hook周期中执行调用 `地图移除该控制组件`
},
methods: {
$_deferredMount() {
// 由于该控制组件已经是挂载到<mapbox-map>的子树下面,this.map已经初始化完毕并注入该组件中了
this.map.addControl(this.control);
if (this.input) {
// 设置默认输入数据
this.control.setInput(this.input);
}
// 发送添加事件. `$_emitEvent` 方法来自 `asControl`
this.$_emitEvent("added", { geocoder: this.control });
this.$_bindSelfEvents(Object.keys(geocoderEvents)); // 把geocode自身的事件映射成vue的事件
this.initial = false; // 初始化完毕
},
$_bindSelfEvents(events) {
// asControl 本身是拥有 $_bindSelfEvents 方法的,但是这里的geocoder组件并不是遵循的mapbox-gl.js的事件机制,
// 因此我们需要覆盖该方法, 按照对应的业务方式实现
const vm = this;
// 使用vue的this.$listeners方式来订阅用户指定的事件
Object.keys(this.$listeners).forEach(eventName => {
if (events.includes(eventName)) {
this.control.on(eventName, vm.$_emitControlEvent.bind(vm, eventName));
}
});
},
// 按照@mapgis/webclient-vue-mapboxgl的规范 发送事件 ,其实就是用{type:eventName}包装事件名
$_emitControlEvent(eventName, eventData) {
return this.$_emitSelfEvent({ type: eventName }, eventData);
},
$_updateInput(results) {
if (!this.initial) {
const input = results.query ? results.query.join("") : "";
this.$emit("update:input", input); // 更新同步属性 "input"
}
}
}
};
TIP
可能看完上面的源码分析,还是有点晕晕的,这里的核心思想本质上和封装官方原始对象是一摸一样的。
唯一的不同之处在于,官方的原始对象监听的事件都是遵循的mapboxgl.js的事件机制,所以封装官方组件的_bindSelfEvents通常是不需要重新实现的,就走内部封装的¥_emitMapEvent方法。
而第三方的插件对象监听的事件 不一定
遵循mapboxgl.js的事件机制,因此需要重新实现bindSelfEvents和封装emitSelfEvent
一言以蔽之:
官方组件事件:
bindSelfEvents => emitSelfEvent不变 => emitMapEvent不变 流程
三方插件事件:
bindSelfEvents新 => emitControlEvent新 => emitSelfEvent不变 => emitMapEvent不变
WARNING
此刻我源代码分析到这里不由得真心感叹一句,这个原始结构设计的真心是 真的即精通mapboxgl又同时精通vue事件,很多GitHub的开源代码往往只是vue/mapboxgl中的某一种进行对应的封装,而这份代码确实是两者都精通,设计上确实高山流水,代码信雅达,因此后面的封装组件强烈建议遵循这一套模式!