我有以下代码呈现 React Leaflet v3 地图并允许用户通过单击地图上的各个点来构建路线。总路线距离显示在地图下方。
interface RouteProps {
updateDistance: (d: number) => void;
}
function Route(props: RouteProps) {
const initialRoute: [number, number][] = [];
const [route, setRoute] = useState(initialRoute);
const calculateDistance = (point1: LatLng, point2: LatLng): number => {
// Omitted for brevity - it returns the distance between point1 and point2
}
const map = useMapEvents({
click(e: any) {
setRoute((prevValue) => {
let lastPoint = null;
if (prevValue.length > 0) {
lastPoint = prevValue[prevValue.length - 1];
const dist = calculateDistance(new LatLng(lastPoint[0], lastPoint[1]), new LatLng(e.latlng.lat, e.latlng.lng));
console.log('marker added, calling updateDistance', dist)
props.updateDistance(dist);
}
return [...prevValue, [e.latlng.lat, e.latlng.lng]];
});
map.panTo(e.latlng);
}
});
return (
<React.Fragment>
{route.map((point, idx) => <Marker key={idx} position={new LatLng(point[0], point[1])}></Marker>)}
<Polyline pathOptions={{fillColor: 'blue'}} positions={route} />
</React.Fragment>
);
}
function App() {
const [distance, setDistance] = useState(0);
const updateDistance = (d: number) => {
console.log('update distance called', d)
setDistance((prevValue) => {
console.log('setting new distance', d);
return prevValue + d
});
};
return (
<React.Fragment>
<MapContainer
center={[55, -2]}
zoom={12}
scrollWheelZoom={false}
style={{ width: "100vw", height: "80vh" }}
>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Route updateDistance={updateDistance} />
</MapContainer>
<h6>Distance: {distance.toFixed(2)} miles</h6>
</React.Fragment>
);
}
可以在这里找到一个 StackBlitz 。请注意,标记无法在地图上正确呈现,但这对于演示目的而言无关紧要。
使用这种方法,我distance
在顶级组件中使用useState
并传递一个处理程序来更新组件的总距离Route
:
const updateDistance = (d: number) => {
console.log('update distance called', d)
setDistance((prevValue) => {
console.log('setting new distance', d);
return prevValue + d
});
};
<MapContainer ...>
...
<Route updateDistance={updateDistance} />
</MapContainer>
其中定义的点击处理程序useMapEvents
更新route
状态(存在于Route
组件中),并且还调用updateDistance
通过传递的函数props
:
const map = useMapEvents({
click(e: any) {
setRoute((prevValue) => {
let lastPoint = null;
if (prevValue.length > 0) {
lastPoint = prevValue[prevValue.length - 1];
const dist = calculateDistance(new LatLng(lastPoint[0], lastPoint[1]), new LatLng(e.latlng.lat, e.latlng.lng));
console.log('marker added, calling updateDistance', dist)
props.updateDistance(dist);
}
return [...prevValue, [e.latlng.lat, e.latlng.lng]];
});
map.panTo(e.latlng);
}
});
当点被添加到路线时,我可以看到updateDistance
(因此setDistance
)被调用了两次。在上面的 StackBlitz 中打开控制台查看输出。
route
我可以通过在顶级组件中定义状态并将其传递给组件来“解决”该问题,Route
如StackBlitz 所示。虽然这可行,但我在组件route
内定义状态和顶级组件中的状态背后的基本原理是,顶级组件只需要知道距离而不是实际路线的细节。Route
distance
我试图理解为什么第一个 StackBlitz 的行为如此。我不相信我在任何地方都在改变状态,并且总是为 theroute
和distance
状态返回一个新值,而且我还在使用setState
状态更新需要使用前一个状态的值的功能版本。