react如何引用(按需加载)百度地图,并结合and组件化封装
1.技术选项:
vite+react+antdesign
load-script
2.实现思路:
1.按需加载如何实现?
要实现按需加载就不能直接在项目的入口文件这种地方去通过script标签引入,这里使用load-script封装了一个加载百度地图的Bmap.js方法,实现动态的插入script脚本。
根目录下创建Bmap.js文件
import _loadScript from 'load-script';
export function Map(url) {
return new Promise((resolve, reject) => {
_loadScript(url, (error, script) => {
if (error) {
return reject(error);
}
console.log('====================================');
console.log(script);
console.log('====================================');
resolve(script);
});
});
}
export default function loadBMap() {
return new Promise(function(resolve, reject) {
if (typeof BMap !== 'undefined') {
resolve(BMap);
return true;
}
window.onBMapCallback = function() {
resolve(BMap);
};
let script = document.createElement('script');
script.type = 'text/javascript';
script.src =
'https://api.map.baidu.com/api?v=3.0&ak=自己的';
script.onerror = reject;
document.head.appendChild(script);
});
}
2.初始化加载地图(注意事项)
要初始化加载地图,我们需要确保的时候,在页面绘制完,或者弹框加载完成之后再去调用初始化的方法,否则就会报错。
报错示例:
Cannot read properties of undefined (reading kc )
封装示例图
3.map.js脚本实现如下:
// 加载百度地图
export function LoadBaiduMapScript() {
console.log("百度地图脚本初始化ing----------------");
const BMap_URL =
"https://api.map.baidu.com/api?v=3.0&ak=换成自己的&callback=onBMapCallback";
return new Promise((resolve, _reject) => {
// 如果已加载直接返回
if (typeof BMap !== "undefined") {
resolve(BMap);
return true;
}
// 百度地图异步加载回调处理
window.onBMapCallback = function () {
console.log("百度地图脚本初始化成功...");
resolve(BMap);
};
// 插入script脚本
const scriptNode = document.createElement("script");
scriptNode.setAttribute("type", "text/javascript");
scriptNode.setAttribute("src", BMap_URL);
document.body.appendChild(scriptNode);
});
}
React实现代码如下:
注意:new BMap 可能会爆红 不用管,不影响使用
地图实例,标记示例,手掌选择实例使用useRef存储吗,使用useState存储会导致内容重新渲染,造成初始化失败
import { Modal, Button, message, Input, Spin } from "antd";
import {
useState,
forwardRef,
useImperativeHandle,
useEffect,
useRef,
} from "react";
import positionIcon from "@/assets/images/position.png";
import { LoadBaiduMapScript } from "./map";
import "./index.less";
interface MapPropsType {
onConfirm: (val) => void;
}
const MapChoice = forwardRef((props: MapPropsType, ref) => {
const { Search } = Input;
const [isModalOpen, setIsModalOpen] = useState(false);
const [searchValue, setSearchValue] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const [address, setAddress] =
useState<string>("河南省郑州市二七区建设东路48号");
const [lon, setLon] = useState<number>(113.65);
const [lat, setLat] = useState<number>(34.76);
// 使用 ref 存储地图相关对象(避免状态异步问题)
const mapRef = useRef<any>(null);
const pointRef = useRef<any>(null);
const markerRef = useRef<any>(null);
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalOpen(false);
const data = { address, lon, lat };
props.onConfirm(data); // 回调方式
};
const handleCancel = () => {
setIsModalOpen(false);
};
useImperativeHandle(ref, () => ({
showModal,
handleOk,
handleCancel,
}));
useEffect(() => {
if (!isModalOpen) return;
const init = async () => {
await LoadBaiduMapScript();
initMap({});
await browserPosition();
};
init();
}, [isModalOpen]);
// 初始化地图
const initMap = (record: any) => {
const currentAddress = record.address || address || "郑州市";
const currentLon = record?.coordinates?.lon ?? lon;
const currentLat = record?.coordinates?.lat ?? lat;
// 创建地图实例(使用 ref 存储)
const mapInstance = new BMap.Map("container");
const pointInstance = new BMap.Point(currentLon, currentLat);
const myIcon = new BMap.Icon(positionIcon, new BMap.Size(23, 25));
const markerInstance = new BMap.Marker(pointInstance, myIcon);
// 存储到 ref
mapRef.current = mapInstance;
pointRef.current = pointInstance;
markerRef.current = markerInstance;
// 初始化地图设置
mapInstance.centerAndZoom(pointInstance, 15);
mapInstance.enableScrollWheelZoom();
mapInstance.addOverlay(markerInstance);
// 更新状态
setAddress(currentAddress);
setLon(currentLon);
setLat(currentLat);
// 绑定点击事件
mapInstance.addEventListener("click", (e: any) => {
const clickedPoint = new BMap.Point(e.point.lng, e.point.lat);
mapInstance.centerAndZoom(clickedPoint, 15);
pointRef.current = clickedPoint;
// 逆地理编码获取地址
const gc = new BMap.Geocoder();
gc.getLocation(clickedPoint, (rs: any) => {
if (rs?.address) {
setAddress(rs.address);
setLon(e.point.lng);
setLat(e.point.lat);
upInfoWindow(rs.address, e.point.lng, e.point.lat);
}
});
});
// 初始信息窗口
upInfoWindow(currentAddress, currentLon, currentLat);
};
// 更新信息窗口
const upInfoWindow = (address: string, lon: number, lat: number) => {
if (!mapRef.current || !pointRef.current) return;
const opts = {
width: 250,
height: 120,
title: "经纬度",
};
const word = `<div>地址:${address}</div>
<div>经度:${lon}</div>
<div>纬度:${lat}</div>`;
const infoWindow = new BMap.InfoWindow(word, opts);
mapRef.current.openInfoWindow(infoWindow, pointRef.current);
markerRef.current.addEventListener("click", () => {
mapRef.current.openInfoWindow(infoWindow, pointRef.current);
});
};
// 地址搜索
const handleSearch = () => {
if (!searchValue?.trim()) {
message.warning("搜索框不能为空");
return;
}
const myGeo = new BMap.Geocoder();
myGeo.getPoint(searchValue.trim(), (point: any) => {
if (point) {
mapRef.current.centerAndZoom(point, 15);
pointRef.current = point;
setLon(point.lng);
setLat(point.lat);
const gc = new BMap.Geocoder();
gc.getLocation(point, (rs: any) => {
if (rs?.address) {
setAddress(rs.address);
upInfoWindow(rs.address, point.lng, point.lat);
}
});
} else {
message.warning("您选择的地址没有解析到结果!");
}
});
};
// 浏览器定位
const browserPosition = async () => {
setLoading(true);
const geolocation = new BMap.Geolocation();
geolocation.getCurrentPosition(async (r: any) => {
if (r?.point) {
const point = new BMap.Point(r.point.lng, r.point.lat);
mapRef.current.centerAndZoom(point, 15);
pointRef.current = point;
const gc = new BMap.Geocoder();
gc.getLocation(point, (rs: any) => {
if (rs?.address) {
setAddress(rs.address);
setLon(r.point.lng);
setLat(r.point.lat);
upInfoWindow(rs.address, r.point.lng, r.point.lat);
}
});
} else {
message.error("定位失败,请手动输入经纬度");
}
setLoading(false);
});
};
return (
<>
<div className="flex items-center">
<div className="mr-2">位置</div>
<Button type="primary" onClick={showModal}>
唤醒地图
</Button>
</div>
<Modal
title="地图选择"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
width={1000}
okText={"确认"}
cancelText={"关闭"}
>
<Spin spinning={loading}>
<Search
placeholder="请输入地址"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
onSearch={handleSearch}
style={{ width: "40%" }}
/>
<div id="container" className="positionbox"></div>
</Spin>
</Modal>
</>
);
});
export default MapChoice;
less样式代码
.positionbox {
width: 100%;
height: 64vh;
margin-top: 20px;
}