本文的核心思想是针对日常开发过程中的实际场景,以mapbox
地图组件为例,结合实际封装过程,由浅入深,提升代码的可读以及可维护性,利民利己。
眨眼间,软件工程毕业五年了。适应工作的变化,尝试了很多角色,也有段时间不怎么实际去写代码,实际参与开发时更多的也是东拼西凑,拿来主义。
对于编程我不是天分选手,属于勤能补拙型,即便如此,除了编码时感觉自己除了能考虑的情况更全面了,很少能在主场找到成就感。趁着国庆假期,既是自我要求,也是顺应项目需求,尝试对mapbox
地图应用组件进行封装,做点有意义的事。
开始前,说一下自己主动想做这件事情的初衷。开发地图相关功能,大致功能无非地图展示以及图标、线路绘制。最初阶段还好,但随着加入功能逐步增多,代码维护已然举步维艰(即便是我自己写的)。因为之前接手过别人杂乱无章的代码,知道有多痛苦,为了不把这份痛苦传递给“下一代”,便开始想着是否可以把一些公共的功能以及配置信息提取出来,让代码看起来更有逻辑,更容易理解和维护(一个5年老码农才有这份意识会不会太迟了?)。
首先能想到的,就是把展示地图所需的token
,key
等信息提取至config.js
中,因为这部分信息几乎独立,当任何地方需要直接引入即可。
const tdtToken = '08f1xxxxxxx'
const pk = "pk.eyJ1IjoiY2FvxxxxxxxxxxxxxxxxxxxxxxxxxxxNxbThnYnB1bHY4In0.eyNoC2s-HIK8YNd-tAfB2g"
export const config = {
pk: pk,
tdTVecUrl: "https://t0.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=" + tdtToken,
tdTFlagUrl: "https://t0.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=" + tdtToken
}
mapbox
提供了丰富的功能调用,对于绘制(点线面)功能,可以单独提取一个组件,如果一味的把繁杂的配置信息添加到主业务文件,会使得代码行数急剧增多,不便于维护,所以新建/utils/DrawTools.js
,同理还可以有RenderTool
等等。由于mapbox
只是匆匆过客,这里并不准备展开介绍他,所以说一下提取过程中自己的一些想法。
es6
的class
包装工具类小tips:
js本身是没有类概念的,但是在es6版本中他贴心的为我们提供了class语法糖,我们不需要关注工具方法是如何放入原型对象中的。
最初我打算将map
对象绑定到window
全局对象中,类似这样:
// 赋值
var map = new mapboxgl(...)
window.mapbox = map
// 其他文件调用
function getMap() {
return window[mapbox]
}
getMap().drawLabel(..)
但是这样一来,如果其他地方使用可能会出现冲突,而且直接将map
对象暴露到全局对象中亦不恰当。所以决定使用类的方式,利用构造函数接受map
对象然后使用。
这里用到了尽量,因为任何操作都会是一把双刃剑,有舍有得,根据实际情况选择即可。
比如封装绘制Marker
时,可预见的需求是绘制独立Marker
或者将多个Marker
一次绘制,但是共享图层id
(方便同步显隐)。
方法大致相同,对于节点我便选择二重数组来接收,可以传多个值[[],[]]
,也可以传单个值[[]]
。
当封装绘制线路的方法时,因为线路有很多的配置信息,比如type、color、layout等等信息,所以不适合单独的接收各个参数,因为会使得参数列表过长,使得调用极不方便,所以最后决定将需要根据需求变动的信息封装到对象中来接收。
/**
*
* @param {站点列表} stations
* @param {绘制线路图层ID} id
* @param {绘制线路选项{layout:{},paint{}}} options
*/
drwaLine(stations, id, options) {}
当我回看这段代码的时候陷入了沉思,封装了,但好像没什么卵用。
flyTo(position,zoom) {
this.map.flyTo({
center: position,
zoom: zoom
})
}
这段是后加的,因为封装时有意使用了英文来表示含义,认为无需对参数进行注释,但是过了2天我发现最初是有失偏颇的…
/**
* 根据提供坐标点,以及图片id,将图片放置到指定位置
* @param {绘制坐标点,支持多个} stations
* @param {绘制图层ID} id
* @param {使用的图片ID} image
* @param {图片尺寸} iconSize
*/
drwaMarker(stations, id, image, iconSize){}
经历了上述过程,代码的可维护性和可读性更强了,但是一些代码依然会显得冗长,比如加载地图,需要先加载资源,然后添加图层,此时难免有违封装的思想,如果可以调用addBaseMap
类似方法,应该能更符合我们的初衷。
将整个map作为一个组件输出,对外暴露addBaseMap
方法,调用方只需要着眼于业务逻辑即可。当我们完成了组件的封装,界面就变成了这个样子(见下方-封装组件),是不是异常清晰,真正做到了各司其职。
由于绘制线路需要传递坐标等信息,所以无法直接封装进map
组件,使用ref
调用一下即可。
地图开发过程中,有一些组件比如测距或者切换地图。如果把这些加到主代码中,我们可能还是会觉得主函数冗长,以测距为例,他至少包含了开始、结束、以及UI
样式等等功能,此时更好的做法是将他封装成一个组件,让代码更简洁的同时,更方便维护,这里是结合Vue slot
(其他框架均可实现)的方式实现。
篇幅有限,为了方便理解,这里是源代码。
mapbox
-- component
-- map.vue #封装自己
-- config
-- config.js #提取配置信息
-- tool
-- DistanceTool.vue #封装组件
-- utils
-- DistanceTool.js #封装工具类
-- DrawTool.js #封装工具类
-- index.vue #应用文件,处理主业务逻辑
欲言者甚多,然不知何言也。