带有标记聚类器的动态谷歌地图标记问题(可能滥用 useEffect/useRef)

问题描述 投票:0回答:1

我正在尝试使用带有标记过滤器的标记聚类器,最初所有标记都显示/列出在地图上,当选中/应用过滤器(复选框输入)时,只应重新渲染标记的子集/添加。当取消选中过滤器时,相应的标记应该重新出现。

最初,所有标记都正确显示,当我应用过滤器时,一切都按预期工作,但是当我取消选中过滤器时,我收到“超出最大更新深度”错误。

"use client";
import { AdvancedMarker, APIProvider, Map, useMap } from "@vis.gl/react-google-maps"
import { MarkerClusterer } from "@googlemaps/markerclusterer";
import type { Marker } from "@googlemaps/markerclusterer";
import { useEffect, useState, useRef } from "react";

const MAP_ID = "xxx";
const API_KEY = "xxx";

type Point = google.maps.LatLngLiteral & {
    key: string,
    city: string
    isCapital: boolean,
};

const locations: Point[] = [
    {
        key: "0",
        city: "Austin",
        lat: 30.241241516441292,
        lng: -97.73378248930749,
        isCapital: true,
    },
    {
        key: "1",
        city: "San Antonio",
        lat: 29.517273972261396,
        lng: -98.44789382382311,
        isCapital: false,
    },
];

type MarkerFilter = "capital" | "all";

export default function Home() {
    const center = { lat: 30.241241516441292, lng: -97.73378248930749 };
    const zoom = 6;
    const [filterCapital, setFilterCapital] = useState(false);
    const [markerFilter, setMarkerFilter] = useState<MarkerFilter>("all");
    useEffect(() => {
        let val: MarkerFilter = "all";
        if (filterCapital) {
            val = "capital";
        }
        setMarkerFilter(() => val);
    }, [filterCapital]);
    return (
        <main>
            <div>
                <label>Capitals</label>
                <input
                    type="checkbox"
                    name="capital"
                    checked={filterCapital}
                    onChange={(e) => setFilterCapital(() => e.target.checked)}
                />
            </div>
            <div style={{ height: "100vh", width: "100%" }}>
                <APIProvider apiKey={API_KEY}>
                    <Map mapId={MAP_ID} defaultCenter={center} defaultZoom={zoom}>
                        <Markers points={locations} filter={markerFilter}/>
                    </Map>
                </APIProvider>
            </div>
        </main>
    );
}

type Props = { points: Point[], filter: MarkerFilter};

const Markers = ({ points, filter }: Props) => {
    const map = useMap();
    const [markers, setMarkers] = useState<{ [key: string]: Marker }>({});
    const clusterer = useRef<MarkerClusterer | null>(null);
    useEffect(() => {
        if (!map) return;
        if (!clusterer.current) {
            clusterer.current = new MarkerClusterer({ map });
        }
    }, [ map ]);
    useEffect(() => {
        clusterer.current?.clearMarkers();
        clusterer.current?.addMarkers(Object.values(markers));
    }, [ markers ]);
    const setMarkerRef = (marker: Marker | null, key: string) => {
        if (marker && markers[key]) return;
        if (!marker && !markers[key]) return;
        setMarkers((prev) => {
            if (marker) {
                return { ...prev, [key]: marker };
            } else {
                const newMarkers = { ...prev };
                delete newMarkers[key];
                return newMarkers;
            }
        });
    }
    return (
        <>
            {points.map((point) => (
                (filter == "all" || (filter == "capital" && point.isCapital)) && <AdvancedMarker
                    key={point.key}
                    position={point}
                    ref={(marker) => setMarkerRef(marker, point.key)}>
                    <div>{point.city}</div>
                </AdvancedMarker>
            ))}
        </>
    );
}
reactjs typescript google-maps-markers
1个回答
0
投票

问题是这段代码:

    const setMarkerRef = (marker: Marker | null, key: string) => {
        if (marker && markers[key]) return;
        if (!marker && !markers[key]) return;
        setMarkers((prev) => {
            if (marker) {
                return { ...prev, [key]: marker };
            } else {
                const newMarkers = { ...prev };
                delete newMarkers[key];
                return newMarkers;
            }
        });
    }

你可以在这里放一个日志来看看,它是在循环运行的。


为了理解原因,让我们重申一下它应该何时运行。 这是一个 callback ref。当它所附加的组件安装时,它的主体将执行。

这是文档中的图像:

When the  DOM node is added to the screen, React will call your ref callback with the DOM node as the argument. When that  DOM node is removed, React will call your ref callback with null.

代码有条件地渲染

AdvancedMarker
:

    return (
        <>
            {points.map((point) => (
                (filter == "all" || (filter == "capital" && point.isCapital)) && <AdvancedMarker
                    key={point.key}
                    position={point}
                    ref={(marker) => setMarkerRef(marker, point.key)}>
                    <div>{point.city}</div>
                </AdvancedMarker>
            ))}
        </>
    );

由于元素

AdvancedMarker
正在安装和卸载,然后再次安装,因此再次调用
setMarkerRef
,进而调用
setMarkers

如果我们查看

setMarkers
主体,它每次都会创建一个新对象(因为它应该用于有效状态更新),从而触发另一个渲染。

如果除了使用

markers
的状态变量之外别无选择,那么最好的选择就是巧妙地设置
markers
。如果每次都创建一个新对象,它将导致重新渲染。尝试利用以前的状态,而不是在值最新时创建新对象。

检查类似下面的内容是否有效:

        setMarkers((prev) => {
            if (marker) { 
                if(prev[key]) return prev;
                return { ...prev, [key]: marker };
            } else { 
                if(!prev[key]) return prev; 
                const newMarkers = { ...prev };
                delete newMarkers[key];
                return newMarkers;
            }
        });

这里是一个 stackblitz,我在其中创建了该问题的最小重现。我也使用相同的技术修复了它。如果您评论该行,您可以看到它会无限渲染。

© www.soinside.com 2019 - 2024. All rights reserved.