如何替换@react-google-maps/api在网络慢/低Chrome版本时的Loading提示
问题描述
我开发了一个基于@react-google-maps/api库渲染地图的网站,正常场景下运行正常,但当网络速度过慢,或者Chrome浏览器版本过低(例如80.xxxx版本)时,地图组件会一直卡在“Loading...”状态(见下图)。

我的实现代码如下:
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { GoogleMap, InfoBox, LoadScript, Marker, useJsApiLoader } from '@react-google-maps/api'; import InfoBoxComponent from '@react-google-maps/api/dist/components/addons/InfoBox'; // other imports export interface InsMapProps { markers: Mkr[]; } const InsMap = ({ markers }: InsMapProps) => { const infoBoxRef = useRef<InfoBoxComponent>(null); const { markerId } = useMarker(); const [isInfoBoxOpen, setIsInfoBoxOpen] = useState<boolean>(false); const [responseMarkers, setResponseMArkers] = useState<Mkr[]>([]); const [selectedMarker, setSelectedMarker] = useState<Mkr | null>(null); const [map, setMap] = useState<google.maps.Map<Element>>(); const [zoom] = useState(14); const mapRef = useRef<google.maps.Map<Element>>(); const { city, district, insType, country, searchStr } = useInsStore(); const { ins } = useInsList(); const [center, setcenter] = useState<TStartPoint>({ lat: 0, lng: 0 }); const apiKey = getGmapsKeys(); const { isLoaded } = useJsApiLoader({ googleMapsApiKey: apiKey }); const version = getBrowser(); const onLoadMap = useCallback( (mapFromLib: google.maps.Map<Element>) => { mapRef.current = mapFromLib; setMap(mapFromLib); }, [map] ); const convertToNumber = (point: TStartPoint): TStartPoint => { const n: TStartPoint = { lat: parseFloat(String(point.lat)), lng: parseFloat(String(point.lng)) }; return n; }; const onMarkerClick = useCallback( (event) => { setSelectedMarker(event); setIsInfoBoxOpen(true); console.log('selected marker=>', event); }, [map] ); const markerType = (obj: any) => { switch (obj.typeId) { case '1001': return Hastane; case '1002': return Eczane; case '1008': return Doktor; default: return Pin; } }; const optionType = (obj: any, index: any) => { const result = { ...obj, id: obj.id, index }; return result; }; const setMarkersFunction = (insListArr: TInstitution[]) => { const tempMarkers: Array<Mkr> = []; insListArr.forEach((marker, index: any) => { if (marker.name.toLocaleLowerCase('tr').includes(searchStr)) { const _marker: Mkr = { position: { lng: Number(marker['lng']), lat: Number(marker['lat']) }, icon: markerType(marker), zIndex: marker.selected ? 999 : 100, options: optionType(marker, index) }; tempMarkers.push(_marker); } }); return tempMarkers; }; const filterAll = async () => { const newOnes = setMarkersFunction(ins); setResponseMArkers([...newOnes]); }; useLayoutEffect(() => { filterAll(); }, [district, insType, markers.length, ins, searchStr]); useEffect(() => { const canPan = selectedMarker !== null && mapRef.current; if (canPan) { mapRef.current?.panTo(selectedMarker.position); } }, [selectedMarker]); useEffect(() => { if (markerId.length > 0) { responseMarkers.forEach((mark: Mkr) => { if (mark.options.id && mark.options.id === markerId) { onMarkerClick(mark); } else { return null; } }); } }, [markerId]); useEffect(() => { const canSetCenter = city !== null; if (canSetCenter) { mapRef.current?.setZoom(12); console.log('center', center); setcenter({ lat: Number(city.coorY), lng: Number(city.coorX) }); } }, [city, district, country]); useEffect(() => { console.log({ district }); const canSetCenter = district !== null && district.code !== '9999'; if (canSetCenter) { mapRef.current?.setZoom(14); setcenter({ lat: Number(district?.coorY), lng: Number(district?.coorX) }); } }, [district]); return ( <div className="InsMap" data-testid="InsMap" id="InsMap"> {isLoaded ? ( <LoadScript onError={(e) => console.log('loadscript error', e)} googleMapsApiKey={getGmapsKeys()} language={'tr-TR'} > <GoogleMap mapContainerStyle={mapContainerStyle} zoom={zoom} options={mapOptions} onLoad={onLoadMap} center={convertToNumber({ lat: center.lat, lng: center.lng })} onCenterChanged={() => mapRef.current?.getCenter().toJSON()} > <MapNav></MapNav> {responseMarkers.map((marker) => { return ( <Marker position={marker.position} key={marker.options.index} icon={marker.icon} zIndex={marker.zIndex} onClick={() => onMarkerClick(marker)} ></Marker> ); })} {isInfoBoxOpen && selectedMarker && selectedMarker?.options?.type !== 'target' && ( <InfoBox options={{ zIndex: 600, pixelOffset: new window.google.maps.Size(-100, 0) }} ref={infoBoxRef} onCloseClick={() => setIsInfoBoxOpen(!isInfoBoxOpen)} position={selectedMarker.position} zIndex={700} > <InsInfoCard selectedMarker={selectedMarker}></InsInfoCard> </InfoBox> )} </GoogleMap> </LoadScript> ) : ( <h4 className="text-center m-12"> Tarayıcı versiyonunuz {version} güncel değildir.<br></br> Bu versiyon harita görünümünü desteklememektedir.<br></br> Lüfen tarayıcınızı güncel bir versiyona yükseltiniz. </h4> )} </div> ); }; export default InsMap;
我希望移除默认的“Loading...”文本,替换为如下自定义提示内容,请问该如何实现?
<h4 className="text-center m-12"> 您的浏览器版本 {version} 已过时。<br></br> 该版本不支持地图显示。<br></br> 请将您的浏览器升级到最新版本。 </h4>
解决方案
你的代码存在两个核心问题导致默认Loading无法替换:
LoadScript与useJsApiLoader重复使用:两者同时存在会导致加载逻辑冲突,默认“Loading...”是LoadScript组件自带的,而你仅在isLoaded为false时展示自定义提示,忽略了LoadScript自身的加载状态。- 状态逻辑混淆:当前代码把“未加载完成”和“浏览器版本过低”混为一谈,网络缓慢时仍会显示默认Loading,而非自定义提示。
修复步骤
- 移除重复加载逻辑:删除
useJsApiLoader相关代码,统一用LoadScript组件管理加载流程,利用它的loadingElement属性自定义加载中内容,通过onLoad和onError处理加载状态。 - 拆分状态逻辑:新增
isMapLoaded(地图加载完成)和isBrowserUnsupported(浏览器不支持)两个状态,分别处理加载中、加载成功、加载失败(版本过低)三种场景。 - 版本判断逻辑:在
LoadScript的onError回调中,结合getBrowser()返回的版本号判断是否为浏览器版本过低导致的加载失败。
修改后的完整代码
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { GoogleMap, InfoBox, LoadScript, Marker } from '@react-google-maps/api'; import InfoBoxComponent from '@react-google-maps/api/dist/components/addons/InfoBox'; // other imports export interface InsMapProps { markers: Mkr[]; } const InsMap = ({ markers }: InsMapProps) => { const infoBoxRef = useRef<InfoBoxComponent>(null); const { markerId } = useMarker(); const [isInfoBoxOpen, setIsInfoBoxOpen] = useState<boolean>(false); const [responseMarkers, setResponseMArkers] = useState<Mkr[]>([]); const [selectedMarker, setSelectedMarker] = useState<Mkr | null>(null); const [map, setMap] = useState<google.maps.Map<Element>>(); const [zoom] = useState(14); const mapRef = useRef<google.maps.Map<Element>>(); const { city, district, insType, country, searchStr } = useInsStore(); const { ins } = useInsList(); const [center, setcenter] = useState<TStartPoint>({ lat: 0, lng: 0 }); // 新增状态:管理加载与错误类型 const [isMapLoaded, setIsMapLoaded] = useState(false); const [isBrowserUnsupported, setIsBrowserUnsupported] = useState(false); const apiKey = getGmapsKeys(); const version = getBrowser(); const onLoadMap = useCallback( (mapFromLib: google.maps.Map<Element>) => { mapRef.current = mapFromLib; setMap(mapFromLib); }, [] ); const convertToNumber = (point: TStartPoint): TStartPoint => { const n: TStartPoint = { lat: parseFloat(String(point.lat)), lng: parseFloat(String(point.lng)) }; return n; }; const onMarkerClick = useCallback( (event) => { setSelectedMarker(event); setIsInfoBoxOpen(true); }, [] ); const markerType = (obj: any) => { switch (obj.typeId) { case '1001': return Hastane; case '1002': return Eczane; case '1008': return Doktor; default: return Pin; } }; const optionType = (obj: any, index: any) => { const result = { ...obj, id: obj.id, index }; return result; }; const setMarkersFunction = (insListArr: TInstitution[]) => { const tempMarkers: Array<Mkr> = []; insListArr.forEach((marker, index: any) => { if (marker.name.toLocaleLowerCase('tr').includes(searchStr)) { const _marker: Mkr = { position: { lng: Number(marker['lng']), lat: Number(marker['lat']) }, icon: markerType(marker), zIndex: marker.selected ? 999 : 100, options: optionType(marker, index) }; tempMarkers.push(_marker); } }); return tempMarkers; }; const filterAll = async () => { const newOnes = setMarkersFunction(ins); setResponseMArkers([...newOnes]); }; useLayoutEffect(() => { filterAll(); }, [district, insType, markers.length, ins, searchStr]); useEffect(() => { const canPan = selectedMarker !== null && mapRef.current; if (canPan) { mapRef.current?.panTo(selectedMarker.position); } }, [selectedMarker]); useEffect(() => { if (markerId.length > 0) { responseMarkers.forEach((mark: Mkr) => { if (mark.options.id && mark.options.id === markerId) { onMarkerClick(mark); } }); } }, [markerId]); useEffect(() => { const canSetCenter = city !== null; if (canSetCenter) { mapRef.current?.setZoom(12); setcenter({ lat: Number(city.coorY), lng: Number(city.coorX) }); } }, [city, district, country]); useEffect(() => { const canSetCenter = district !== null && district.code !== '9999'; if (canSetCenter) { mapRef.current?.setZoom(14); setcenter({ lat: Number(district?.coorY), lng: Number(district?.coorX) }); } }, [district]); // 处理加载错误:判断是否为浏览器版本过低 const handleLoadError = (error) => { console.log('loadscript error', error); const chromeVersion = parseInt(version.split('.')[0], 10); // 这里可根据实际支持的最低版本调整阈值 if (chromeVersion < 90) { setIsBrowserUnsupported(true); } }; return ( <div className="InsMap" data-testid="InsMap" id="InsMap"> {isBrowserUnsupported ? ( <h4 className="text-center m-12"> 您的浏览器版本 {version} 已过时。<br></br> 该版本不支持地图显示。<br></br> 请将您的浏览器升级到最新版本。 </h4> ) : ( <LoadScript googleMapsApiKey={apiKey} language={'tr-TR'} onLoad={() => setIsMapLoaded(true)} onError={handleLoadError} // 自定义加载中提示内容 loadingElement={ <h4 className="text-center m-12"> 地图加载中,请稍候... </h4> } > {isMapLoaded && ( <GoogleMap mapContainerStyle={mapContainerStyle} zoom={zoom} options={mapOptions} onLoad={onLoadMap} center={convertToNumber({ lat: center.lat, lng: center.lng })} onCenterChanged={() => mapRef.current?.getCenter().toJSON()} > <MapNav></MapNav> {responseMarkers.map((marker) => ( <Marker position={marker.position} key={marker.options.index} icon={marker.icon} zIndex={marker.zIndex} onClick={() => onMarkerClick(marker)} /> ))} {isInfoBoxOpen && selectedMarker && selectedMarker?.options?.type !== 'target' && ( <InfoBox options={{ zIndex: 600, pixelOffset: new window.google.maps.Size(-100, 0) }} ref={infoBoxRef} onCloseClick={() => setIsInfoBoxOpen(false)} position={selectedMarker.position} zIndex={700} > <InsInfoCard selectedMarker={selectedMarker} /> </InfoBox> )} </GoogleMap> )} </LoadScript> )} </div> ); }; export default InsMap;
关键修改说明
- 移除
useJsApiLoader,统一使用LoadScript组件管理地图加载流程 - 新增两个状态变量,分别区分地图加载完成状态和浏览器不支持状态
- 通过
LoadScript的loadingElement属性替换默认“Loading...”文本 - 在
onError回调中加入版本判断逻辑,触发浏览器不支持提示 - 调整渲染优先级:先判断浏览器是否不支持,再渲染加载组件,
相关产品推荐
相关产品推荐

