All files / api-loader/src index.tsx

0% Statements 0/69
0% Branches 0/73
0% Functions 0/15
0% Lines 0/66

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
/// <reference types="@uiw/react-baidu-map-types" />
 
/**
 * 初始化和加载baidu地图
 */
import React from 'react';
import { requireScript } from '@uiw/react-baidu-map-utils';
 
declare global {
  interface Window {
    BMap: any;
    [index: string]: () => any; // or just any
  }
}
 
export interface APILoaderConfig {
  /**
   * akay 密钥
   * 您需先申请密钥(ak)才可使用该服务,接口无使用次数限制,请开发者放心使用。
   * 文档说明地址:http://lbsyun.baidu.com/index.php?title=jspopular3.0/guide/getkey
   * 申请秘钥地址:http://lbs.baidu.com/apiconsole/key?application=key
   */
  akay?: string;
  /**
   * SDK 包版本
   * @default 3.0
   */
  version?: string;
  /**
   * 协议,默认是根据当前网站协议的
   */
  protocol?: 'http' | 'https';
  /**
   * 请求 SDK 的前半部分
   * http://api.map.baidu.com/api?v=2.0&ak=E480562045
   * @default api.map.baidu.com/api
   */
  hostAndPath?: string;
  /**
   * JSONP 回调函数
   */
  callbackName?: string;
  /**
   * 百度地图JavaScript API GL v1.0是一套由JavaScript语言编写的应用程序接口,可帮助您在网站中构建功能丰富、交互性强的地图应用,支持PC端和移动端基于浏览器的地图应用开发,且支持HTML5特性的地图开发。
   * JavaScript API GL使用了WebGL对地图、覆盖物等进行渲染,支持3D视角展示地图。 GL版本接口基本向下兼容,迁移成本低。目前v1.0版本支持了基本的3D地图展示、基本地图控件和覆盖物。
   */
  type?: 'webgl';
  /**
   * 禁用 SDK 加载,当你使用本地自己加载 SDK 可以设置为 true
   * ```html
   * <script type="text/javascript" src="//api.map.baidu.com/api?v=2.0&ak=您的密钥"></script>
   * ```
   */
  disableScripts?: boolean;
}
 
export function delay(time: number): Promise<undefined> {
  return new Promise((resolve, reject) => {
    window && window.setTimeout(resolve, time);
  });
}
 
export interface APILoaderProps extends APILoaderConfig {
  /**
   * 用于展示加载中或错误状态
   */
  fallback?: (error?: Error) => React.ReactNode;
}
 
interface State {
  loaded: boolean;
  error?: Error;
}
 
const DEFAULT_RETRY_TIME = 3;
 
/**
 * APILoader 用于加载百度地图依赖
 */
export default class APILoader extends React.Component<React.PropsWithChildren<APILoaderProps>, State> {
  public static defaultProps = {
    akay: '',
    hostAndPath: 'api.map.baidu.com/api',
    version: '2.0',
    callbackName: 'load_bmap_sdk',
    type: '',
  };
 
  private isMountedOk: boolean = false;
 
  /**
   * 全局可能存在多个Loader同时渲染, 但是只能由一个负责加载
   */
  private static waitQueue: Array<[Function, Function]> = [];
  /**
   * 等待BMap就绪
   */
  public static async ready() {
    if (window && window.BMap.Map) {
      return;
    }
    if (window && window.BMapGL.Map) {
      return;
    }
    return new Promise((res, rej) => {
      APILoader.waitQueue.push([res, rej]);
    });
  }
  public constructor(props: APILoaderProps) {
    super(props);
    this.state = {
      loaded:
        props.type === 'webgl'
          ? window && !!window.BMapGL && !!window.BMapGL.Map
          : window && !!window.BMap && !!window.BMap.Map,
    };
    if (this.props.akay == null) {
      throw new TypeError('BaiDuMap: akay is required');
    }
  }
 
  public componentDidMount() {
    this.isMountedOk = true;
    const { callbackName } = this.props;
    if (!window || !callbackName) {
      return;
    }
    if (
      (this.props.type === 'webgl' && window && window.BMapGL && !window.BMapGL.Map) ||
      (this.props.type === 'webgl' && window && !window.BMapGL) ||
      (window && window.BMap && !window.BMap.Map) ||
      (window && !window.BMap)
    ) {
      if (window && window[callbackName]) {
        APILoader.waitQueue.push([this.finish, this.handleError]);
        return;
      }
 
      this.loadMap();
    }
  }
 
  componentWillUnmount() {
    this.isMountedOk = false;
  }
 
  public render() {
    if (window && window.BMapGL && this.props.type === 'webgl') {
      window.BMap = window.BMapGL as any;
    }
    return this.state.loaded ? (
      this.props.children
    ) : this.props.fallback ? (
      this.props.fallback(this.state.error)
    ) : this.state.error ? (
      <div style={{ color: 'red' }}>{this.state.error.message}</div>
    ) : null;
  }
 
  private getScriptSrc() {
    const cfg = this.props;
    let protocol = (cfg.protocol || window.location.protocol) as APILoaderConfig['protocol'];
    if (protocol!.indexOf(':') === -1) {
      protocol += ':';
    }
    const args = [`v=${cfg.version}`, `ak=${cfg.akay}`, `callback=${cfg.callbackName}`];
    if (cfg.type) {
      args.push(`type=${cfg.type}`);
    }
    return `${protocol}//${cfg.hostAndPath}?${args.join('&')}`;
  }
  /**
   * load BaiduMap in script tag
   */
  private async loadMap() {
    const { callbackName } = this.props;
    if (!window || !callbackName) {
      return;
    }
    const src = this.getScriptSrc();
    window[callbackName] = () => {
      // flush queue
      const queue = APILoader.waitQueue;
      APILoader.waitQueue = [];
      queue.forEach((task) => task[0]());
      this.finish();
    };
 
    for (let i = 0; i < DEFAULT_RETRY_TIME; i++) {
      try {
        await requireScript(src);
        break;
      } catch (error: any) {
        if (i === DEFAULT_RETRY_TIME - 1) {
          const err = new Error(`Failed to load Baidu Map: ${error.message}`);
          // flush queue
          const queue = APILoader.waitQueue;
          APILoader.waitQueue = [];
          queue.forEach((task) => task[1](err));
          this.handleError(err);
          return;
        }
        await delay(i * 1000);
      }
    }
  }
 
  private handleError = (error: Error) => {
    if (this.isMountedOk) {
      this.setState({ error });
    }
  };
 
  private finish = () => {
    if (window && this.props.type === 'webgl') {
      window.BMap = window.BMapGL as any;
    }
    if (this.isMountedOk) {
      this.setState({
        loaded: true,
      });
    }
  };
}