1. 需求
项目中只有一个模块需要加载SDK,所以没必要直接写在入口html文件中,用户打开这个模块才加载地图文件这样才合适。
注意:sdk版本是webgl版,不过原理都一样。
2. 原理
代码参考的是高德地图的loader,原理很简单:
- 本质上就是需要的时候,动态添加
script标签 - 百度地图请求
sdk的时候可以加一个回调函数参数'//api.map.baidu.com/api?type=webgl&v=1.0&ak=您的密钥&callback=init',这里的init就是加载完成后会调用的函数,在init中就能获取BMap对象 - 同时需要保存一个状态来判断之前是否有加载过地图,或者加载失败,是否需要重试
3. 参考
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.notLoadsdk没有加载过,执行加载逻辑。加载逻辑后面分析。 -
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;
}