React Leaflet v3 点击地图构建路线时setState触发两次问题排查
问题原因
- 核心问题是你将带有副作用的
props.updateDistance(dist)调用放在了setRoute的状态更新回调函数中。
React官方要求传入setState的更新函数必须是纯函数,不能包含任何副作用(包括调用外部方法、修改外部变量、打印日志等操作)。在React 18及以上版本的严格模式下,React会主动执行两次状态更新函数,用于排查开发者编写的非纯函数逻辑,这就是你看到updateDistance被调用两次的直接原因。 - 次要原因是你在App组件中定义的
updateDistance函数没有做缓存,每次App组件重渲染时都会生成新的函数实例,导致Route组件接收的props变化触发重渲染,也可能额外触发事件绑定的重复执行。
解决方案
你不需要把route状态移到父组件,只要调整副作用的执行位置即可,完全保留Route组件内部维护route状态的设计:
- 把距离计算和
updateDistance调用从setRoute的更新回调中移到useEffect中,监听route状态变化后再执行对应逻辑 - 用
useCallback缓存App组件中的updateDistance函数,避免不必要的重渲染
修改后的核心代码示例
Route组件调整
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 => { // 原有距离计算逻辑不变 } const map = useMapEvents({ click(e: any) { // 状态更新回调仅做纯逻辑,返回新的状态值 setRoute((prevValue) => [...prevValue, [e.latlng.lat, e.latlng.lng]]); map.panTo(e.latlng); } }); // 用useEffect处理副作用:状态变化后计算距离并通知父组件 useEffect(() => { if (route.length < 2) return; const lastPointIndex = route.length - 1; const prevPoint = route[lastPointIndex - 1]; const currentPoint = route[lastPointIndex]; const dist = calculateDistance( new LatLng(prevPoint[0], prevPoint[1]), new LatLng(currentPoint[0], currentPoint[1]) ); props.updateDistance(dist); }, [route, props.updateDistance]); return ( <> {route.map((point, idx) => <Marker key={idx} position={new LatLng(point[0], point[1])} />)} <Polyline pathOptions={{fillColor: 'blue'}} positions={route} /> </> ); }
App组件调整
function App() { const [distance, setDistance] = useState(0); // 用useCallback缓存更新函数,避免不必要的重渲染 const updateDistance = useCallback((d: number) => { setDistance((prevValue) => prevValue + d); }, []); return ( <> <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> </> ); }
内容的提问来源于stack exchange,提问作者Ian A
相关产品推荐
相关产品推荐

