高德 JSApi + L7 + Vue3.x hook 地图开发体验优化

高德 JSApi + L7 + Vue3.x hook 地图开发体验优化

Author: Charles Chan

Date: 2023-07-28

本文为开放式文章,不限于发布内部平台,将在社区进行分享

前言

高德地图是现在市面上最好的 webGis 框架之一,但不可避免的他也存在一定的缺点。

场景一: 高德地图 JSAPI 类型推断糟糕

高德地图 JSAPI 由于是比较底层的 JavaScript 写法写的,导致在工程上的类型推断十分糟糕,对开发者来说容易产生bug缺陷,这种问题在 typescript 工程更加明显。

虽然高德官方自己为 typescript 类型推断添加了一个 npm 包,里面包含了大部分的 AMap 方法声明,但从下载量就可以看得出这东西其实不太好用。

场景二: 一些高级用法需要借用 Loca / L7

通过高德地图 JSAPI 其实是比较难实现一些高交互和酷炫的操作的。可以理解为高德地图其实真正的功能就是充当底图的作用。如果要实现一些比较酷炫的大屏样式,不可避免的要在 Loca 或 L7 进行选择。

Loca

实际体验发现,其实 Loca 还是存在"场景一"那样的类型推断问题,类型声明十分糟糕,并且官方对 Loca 的一些 api 说明也难以阅读。这是我放弃使用 Loca 的原因。

有些人会说 Loca 对高德地图 JSApi 的兼容更好,但后面我发现 L7 也差不到哪里去。

Antv L7

L7 和 Loca 同为阿里系的地图可视化框架,但为不同团队编写。从开发体验上来说, L7 拥有更好的 typescript 支持和更多拓展性。后期在私有化部署上,甚至可以不用高德底图。

一般在 L7 和 Loca 取舍是因为有些人会觉得用了 L7 无法使用高德地图 JSAPI,其实并不然。通过官方文档分析,其实 L7 是将 map 实例挂载在 scene 变量下,AMap 实例则和高德地图 JSApi 一样挂载在 window 下。

在本人编写的 hook 下,我将 scene.map 赋值与 map 响应式数据,再从 window 下拿 AMap,经过实验,所有原生高德地图 JSAPI 方法均奏效。

小结

综上所述,我使用了 L7 + hook + JSApi 方法进行封装,不仅有利于提高开发时高德地图api的类型推断,并且能有效拓展地图开发能力。

封装思路

创建 useMap hook,并通过引入 @amap/amap-jsapi-types 对原生 JSApi 进行再度封装。

示例一: 高德地图 JSAPI 创建地图

例如使用高德地图 JSAPI 创建地图封装方法:

typescript 复制代码
  /**
   * 初始化地图函数
   * @param elementId
   * @param plugins
   * @param options
   * @param hooks
   * @returns
   */
  const initMap = (
    elementId: string,
    plugins?: Array<string>,
    options: AMap.MapOptions = {
      // 设置地图容器id
      viewMode: '3D', // 是否为3D地图模式
      zoom: 18, // 初始化地图级别
      rotation: 30, // 地图平面旋转
      pitch: 60, // 地图视角
      zooms: [8, 22],
      mapStyle: 'amap://styles/grey',
      center: [113.33, 23.11] // 初始化地图中心点位置
    },
    hooks?: MapHooks
  ): Promise<MapResult> => {
    return new Promise((resolve, reject) => {
      const targetWindow = window as any
      targetWindow._AMapSecurityConfig = {
        securityJsCode: code
      }

      AMapLoader.load({
        key: key, // 申请好的Web端开发者Key,首次调用 load 时必填
        version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
        plugins // 需要使用的的插件列表,如比例尺'AMap.Scale'等
      })
        .then((AMap) => {
          if (hooks && hooks.beforeMapCreate) {
            hooks.beforeMapCreate(AMap)
          }

          map.value = new AMap.Map(elementId, options)

          if (hooks && hooks.afterMapCreated) {
            hooks.afterMapCreated(AMap, map)
          }

          resolve({
            AMap,
            map
          })
        })
        .catch((error) => {
          console.error('高德地图初始化失败', error)
          reject(error)
        })
    })
  }

优点

1. 类型推断

在这里使用了 AMap.MapOptions 和 MapResult 类型声明,开发者在编辑器调用该方法,会自动提示地图创建时可以传入哪些参数。

2. 避免 AMap 变量覆盖

函数最后返回了 AMap 和 map 实例,开发者不用顾及 AMap 是否被 window 其他页面覆盖,以及 map 实例的调用。只要在调用hook内置函数时,传入当前通过 initMap 返回的 AMap 变量即可。

示例二:封装创建信息窗体方法

如下方法所示,该方法兼容了传入 content 和 options 两种情况,并且在调用创建信息窗体方法时,编辑器会自动对 AMap.InfoOptions 进行类型提示,且返回的信息窗体示例也带有类型提示。

php 复制代码
  /**
   * 创建信息窗体
   * @param AMap
   * @param options
   * @returns
   */
  const createInfoWindow = (AMap: any, options: string | AMap.InfoOptions): AMap.InfoWindow => {
    if (typeof options === 'string') {
      return new AMap.InfoWindow({
        isCustom: true, // 使用自定义窗体
        content: options, // 信息窗体的内容可以是任意 html 片段
        offset: new AMap.Pixel(16, -45)
      })
    } else {
      return new AMap.InfoWindow(options)
    }
  }

优点

1. 入参类型推断

通过封装,调用函数时,能有效提示开发者应该传入什么参数,避免使用错误导致的创建信息窗体执行报错。

2. 返回示例具备类型提示

返回值通过 typescript 对返回值进行断言,当开发者使用该方法进行创建信息窗体时,被赋值的变量自带类型提示。

示例三: L7 创建地图封装

通过 L7 的创建 scene,监听 loaded 事件,promise返回 AMap 以及 map 示例等等。

typescript 复制代码
  /**
   * 加载 L7 地图并兼容原生 hook 方法
   * @param elementId
   * @param plugins
   * @param options
   * @param hooks
   * @returns
   */
  const initL7Map = (
    elementId: string,
    plugins?: Array<string>,
    options: ISceneConfig = {
      id: elementId,
      map: new GaodeMap({
        pitch: 35.210526315789465,
        style: 'dark',
        center: [104.288144, 31.239692],
        zoom: 4.4,
        token: key,
        plugin: plugins
      })
    },
    hooks?: MapHooks
  ) => {
    return new Promise((resolve) => {
      const targetWindow = window as any
      targetWindow._AMapSecurityConfig = {
        securityJsCode: code
      }

      if (hooks && hooks.beforeMapCreate) {
        hooks.beforeMapCreate(AMap)
      }

      const scene = new Scene(options)

      scene.on('loaded', () => {
        map.value = scene.map

        if (hooks && hooks.afterMapCreated) {
          hooks.afterMapCreated(window.AMap, map)
        }

        resolve({
          AMap: window.AMap,
          map,
          scene
        })
      })
    })
  }

优点

1. 方法与 hook 内置函数通用兼容

从写法上看,和 JSAPI 封装方法逻辑基本一致。且暴露 AMap 和 map 示例,对 hook 内置的方法充分利用兼容。如下图所示,创建信息窗体方法正常执行并渲染显示。

缺点

1. 暴露的 AMap 为window 全局

由于 L7 创建的 AMap 同 JSAPI 一样都是挂载到 window,该返回的 AMap 是处于 window.AMap 下的,在多页面地图切换交互时,可能会出现问题(其实本来就会存在,也不算问题),需要一些魔法操作。

总结

至此,技术分享结束,由于编写的hook 接近 1000 行代码,这些不进行展示,后续考虑发布 npm 包依赖。

相关推荐
喵叔哟12 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
man20171 小时前
【2024最新】基于springboot+vue的闲一品交易平台lw+ppt
vue.js·spring boot·后端
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js