likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 24

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

Author: Charles Chan

Date: 2023-07-28

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

前言

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

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

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

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

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

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

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

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

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 方法均奏效。

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

小结

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

封装思路

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

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

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

  /**
   * 初始化地图函数
   * @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 进行类型提示,且返回的信息窗体示例也带有类型提示。

  /**
   * 创建信息窗体
   * @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 对返回值进行断言,当开发者使用该方法进行创建信息窗体时,被赋值的变量自带类型提示。

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

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

示例三: L7 创建地图封装

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

  /**
   * 加载 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 内置的方法充分利用兼容。如下图所示,创建信息窗体方法正常执行并渲染显示。

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

缺点

1.  暴露的 AMap 为window 全局

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

总结

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

转载自:https://juejin.cn/post/7260628671600885818
评论
请登录