异步按需加载百度地图jsSDK

626 阅读2分钟

1. 需求

项目中只有一个模块需要加载SDK,所以没必要直接写在入口html文件中,用户打开这个模块才加载地图文件这样才合适。

注意:sdk版本是webgl,不过原理都一样。

2. 原理

代码参考的是高德地图的loader,原理很简单:

  1. 本质上就是需要的时候,动态添加script标签
  2. 百度地图请求sdk的时候可以加一个回调函数参数'//api.map.baidu.com/api?type=webgl&v=1.0&ak=您的密钥&callback=init',这里的init就是加载完成后会调用的函数,在init中就能获取BMap对象
  3. 同时需要保存一个状态来判断之前是否有加载过地图,或者加载失败,是否需要重试

3. 参考

百度地图文档

高德loader

4. 完整代码

type BMapLoaderfunc = (options: BMapLoaderOptions) => Promise<void>;
interface BMapLoaderOptions {
  key: string;
  version?: string;
}

enum LoadStatus {
  notLoad,
  loading,
  success,
  failed,
}
const STATUS = {
  MAP: LoadStatus.notLoad,
};
/**
 * 异步加载地图js
 *
 * 使用了异步加载之后,在使用`BMapGL`的时候请注意是否已经加载完成
 *
 * 最好是在`BMap`组件的`onLoad`事件里面初始化`BMapGL`的对象
 */
const BMapLoader: BMapLoaderfunc = (options) =>
  new Promise((resolve, reject) => {
    if (STATUS.MAP === LoadStatus.loading) {
      reject('不允许加载多个不同版本类型的地图');
    }

    if (STATUS.MAP === LoadStatus.notLoad) {
      //初次加载
      let { key, version = '1.0' } = options;
      if (!key) {
        reject('请填写key');
        return;
      }
      if (window.BMapGL && Object.keys(BMapGL).length <= 1) {
        // 这种情况可能是js脚本没有下载或者报错了,只加载了一个只有一个apiLoad方法的BMapGL,
        // 这种情况下不会调用定义的回调函数,所以状态还是notLoad。刷新页面基本上可以解决
        // 重试
        // 移除jssdk的script标签
        document.querySelector('scrtpt[src*="http://api.map.baidu.com/getscript"]')?.remove();
        document.querySelector('scrtpt[src*="//api.map.baidu.com/api?"]')?.remove();
        // @ts-ignore
        delete window.BMapGL;
        BMapLoader(options);
        return;
      }
      STATUS.MAP = LoadStatus.loading;

      const parentNode = document.body || document.head;

      // 参考:https://lbsyun.baidu.com/jsdemo.htm#aAsynLoadMap
      window.___onAPILoaded = function () {
        delete window.___onAPILoaded;
        STATUS.MAP = LoadStatus.success;
        resolve();
      };
      const script = document.createElement('script');
      script.setAttribute('id', 'baidu-map');
      script.type = 'text/javascript';

      script.src = `//api.map.baidu.com/api?type=webgl&v=${version}&ak=${key}&callback=___onAPILoaded`;
      script.onerror = (e) => {
        STATUS.MAP = LoadStatus.failed;
        reject(e);
      };
      parentNode.appendChild(script);
    }
    if (STATUS.MAP === LoadStatus.success) {
      resolve();
    }
    if (STATUS.MAP === LoadStatus.failed) {
    }
  });
export default BMapLoader;

// 使用
  useEffect(() => {
    BMapLoader({
      key: '秘钥',
    }).then(() => {
      mapInstance.current = new BMapGL.Map(容器, {地图配置});
    });
    return () => {
      mapInstance.current?.destroy();
    };
  }, []);

5. 分析

整个loader就是根据不同状态执行不同动作。

enum LoadStatus {
  notLoad,
  loading,
  success,
  failed,
}
const STATUS = {
  MAP: LoadStatus.notLoad,
};
const BMapLoader: BMapLoaderfunc = (options) =>
  new Promise((resolve, reject) => {
    if (STATUS.MAP === LoadStatus.loading) {
    }
    if (STATUS.MAP === LoadStatus.notLoad) {
    }
    if (STATUS.MAP === LoadStatus.success) {
    }
    if (STATUS.MAP === LoadStatus.failed) {
    }
  });
  • STATUS.MAP === LoadStatus.loading

    此时sdk正在加载,不允许同时加载多个

  • STATUS.MAP === LoadStatus.notLoad

    sdk没有加载过,执行加载逻辑。加载逻辑后面分析。

  • STATUS.MAP === LoadStatus.success

    已经加载过了,就直接resolve

  • STATUS.MAP === LoadStatus.failed

    加载失败,这里可以加一个重试的功能

5.1 加载逻辑

      const script = document.createElement('script');
      script.setAttribute('id', 'baidu-map');
      script.type = 'text/javascript';

      script.src = `//api.map.baidu.com/api?type=webgl&v=${version}&ak=${key}&callback=___onAPILoaded`;
      script.onerror = (e) => {
        STATUS.MAP = LoadStatus.failed;
        reject(e);
      };
      parentNode.appendChild(script);

核心就是创建script元素,加载sdk,如果失败就将状态置为failed,如果成功sdk会调用回调函数___onAPILoaded

// ___onAPILoaded
 window.___onAPILoaded = function () {
     // 加载完成后这个回调就没有用了
    delete window.___onAPILoaded;
    STATUS.MAP = LoadStatus.success;
    resolve();
  };

这里有个特殊的问题,百度地图sdk下载成功,但是没加载完全,此时状态就是未加载。如果下次请求没有区分这个状态,再次引入会有问题。所以通过判断BMapGL的属性数量来确定是否加载完全。

  if (window.BMapGL && Object.keys(BMapGL).length <= 1) {
    document.querySelector('scrtpt[src*="http://api.map.baidu.com/getscript"]')?.remove();
    document.querySelector('scrtpt[src*="//api.map.baidu.com/api?"]')?.remove();
    delete window.BMapGL;
    BMapLoader(options);
    return;
  }