You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何替换@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无法替换:

  1. LoadScript与useJsApiLoader重复使用:两者同时存在会导致加载逻辑冲突,默认“Loading...”是LoadScript组件自带的,而你仅在isLoaded为false时展示自定义提示,忽略了LoadScript自身的加载状态。
  2. 状态逻辑混淆:当前代码把“未加载完成”和“浏览器版本过低”混为一谈,网络缓慢时仍会显示默认Loading,而非自定义提示。

修复步骤

  1. 移除重复加载逻辑:删除useJsApiLoader相关代码,统一用LoadScript组件管理加载流程,利用它的loadingElement属性自定义加载中内容,通过onLoad和onError处理加载状态。
  2. 拆分状态逻辑:新增isMapLoaded(地图加载完成)和isBrowserUnsupported(浏览器不支持)两个状态,分别处理加载中、加载成功、加载失败(版本过低)三种场景。
  3. 版本判断逻辑:在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回调中加入版本判断逻辑,触发浏览器不支持提示
  • 调整渲染优先级:先判断浏览器是否不支持,再渲染加载组件,
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 13:46:24