我正在尝试使用带有标记过滤器的标记聚类器,最初所有标记都显示/列出在地图上,当选中/应用过滤器(复选框输入)时,只应重新渲染标记的子集/添加。当取消选中过滤器时,相应的标记应该重新出现。
最初,所有标记都正确显示,当我应用过滤器时,一切都按预期工作,但是当我取消选中过滤器时,我收到“超出最大更新深度”错误。
"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>
))}
</>
);
}
问题是这段代码:
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。当它所附加的组件安装时,它的主体将执行。
这是文档中的图像:
代码有条件地渲染
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,我在其中创建了该问题的最小重现。我也使用相同的技术修复了它。如果您评论该行,您可以看到它会无限渲染。