react-bootstrap手风琴绑定Leaflet标记缩放需双击问题求解
问题根因
- 核心问题:你将
Home组件嵌套定义在了App组件内部,每次App的linkName状态更新时,会触发App重渲染,Home组件会被完全重建,内部手风琴的展开/折叠状态直接被重置,第一次点击的生效结果被覆盖,因此需要第二次点击才能正常响应。 - 次要问题:地图定位逻辑和标记渲染逻辑耦合,
linkName变化时没有单独的监听逻辑触发定位,同时标记重复渲染没有清理旧实例,会造成内存泄漏和功能异常。
解决方案
1. 拆解嵌套组件,将Home组件移出App组件
子组件不能定义在父组件内部,否则父组件状态更新会导致子组件完全重建,所有内部状态丢失。修改后App组件代码如下:
// 将Home组件移到App外部定义 const Home = ({ initialMarkersList, linkName, handleLink, clicked, setClicked }) => { return ( <div className="App"> <div className="links"> <Accordion> { initialMarkersList && initialMarkersList.map( ( link ) => { return <AccordionComp key={ link.id } link={ link } handleLink={ handleLink } /> } ) } </Accordion> </div> <MapContainer data={ initialMarkersList } linkName={ linkName } clicked={ clicked } setClicked={ setClicked } /> </div> ) } const App = () => { const [ clicked, setClicked ] = useState( false ) const [ linkName, setLinkName ] = useState( null ) const initialMarkersList = localStorage.getItem( "markersList" ) ? JSON.parse( localStorage.getItem( "markersList" ) ) : []; const handleLink = ( e, linkName ) => { setLinkName( linkName ) } useEffect( () => { console.log( document.getElementsByClassName( 'accordion-collapse' )[ 0 ]?.classList ); }, [] ) return ( <> <Switch> <Route path="/" exact render={() => <Home initialMarkersList={initialMarkersList} linkName={linkName} handleLink={handleLink} clicked={clicked} setClicked={setClicked} />} /> <Route path="/add-marker" exact component={ FormMarker } /> </Switch > </> ) } export default App;
2. 优化地图组件逻辑,解耦定位和渲染逻辑
在MapContainer中新增标记存储、旧标记清理、linkName单独监听逻辑:
const MapContainer = ( { data, linkName, formCss, addMarker, isMarkerAdded, setIsMarkerAdded, formData, setFormData, setGetMarkerObject, setMarkersList, setEdit, clicked, setClicked } ) => { const [ coordinates, setCoordinates ] = useState( { lats: 0, lngs: 0 } ) const [markers, setMarkers] = useState([]) const mapRef = useRef(null) // 用useRef存储map实例代替全局变量 const MaxMapMBounds = [ [ 0.047356142676201576, -0.05648240856298255 ], [ -0.09911669921875, 0.05903846615873286 ] ] const buildMap = () => { const map = L.map( 'map', { crs: L.CRS.Simple, maxBounds: MaxMapMBounds, minZoom: 13, maxZoom: 15, maxBoundsViscosity: 0.0, } ).setView( [ 0, 0 ], 13 ); // 原有地图初始化逻辑不变 var imageBounds = [ [ 0.0484693481114499, -0.0652342559975141 ], [ -0.03143095517869242, 0.06518541415112039 ] ]; const image = L.imageOverlay( masterplan, imageBounds, { className: 'image-map' }, ).addTo( map ); map.on( 'click', function ( e ) { var coord = e.latlng; var lat = coord.lat; var lng = coord.lng; console.log( `latitude: ${ lat } and longitude: ${ lng } === [${ lat } , ${ lng } ]` ); } ); mapRef.current = map return map } const renderMarkers = () => { const map = mapRef.current // 先清空已有标记 markers.forEach(marker => map.removeLayer(marker)) const newMarkers = [] data && data.map( ( link ) => { let marker if ( addMarker ) { // 原有创建带编辑/删除按钮标记的逻辑不变 var container = L.DomUtil.create( 'div' ); const createButton = ( label, container, className ) => { var btn = L.DomUtil.create( 'button', '', container ); btn.setAttribute( 'type', 'button' ); btn.setAttribute( 'class', className ); btn.innerHTML = label; return btn; } const createParagraph = ( label, container, className ) => { var para = L.DomUtil.create( 'p', '', container ); para.setAttribute( 'class', className ); para.innerHTML = label; return para; } const editButton = createButton( 'Edit marker', container, 'edit-button' ); const removeButton = createButton( 'Remove marker', container, 'remove-button' ); const popupText = createParagraph( `Name: ${ link.name }`, container, 'popup-text' ) L.DomEvent.on( editButton, 'click', () => handleEditMarker( marker ) ); L.DomEvent.on( removeButton, 'click', () => handleRemoveMarker( marker ) ); marker = L.marker( L.latLng( link.markerPosition ), { icon: myIcon } ).addTo( map ).bindPopup( container ) } else { marker = L.marker( L.latLng( link.markerPosition ), { icon: myIcon } ).addTo( map ).bindPopup( link.markerText ) } newMarkers.push(marker) }) setMarkers(newMarkers) } // 新增单独监听linkName的逻辑,处理标记定位 useEffect(() => { const map = mapRef.current if(!linkName || !map || !data || markers.length === 0) return const targetLink = data.find(item => item.name === linkName) if(!targetLink) return const targetMarker = markers.find(m => m._latlng.lat === targetLink.markerPosition[0] && m._latlng.lng === targetLink.markerPosition[1] ) if(targetMarker) { targetMarker.openPopup() const unitMarker = L.polygon( targetLink.latlng, { fill: false, opacity: 0 } ).addTo( map ) map.fitBounds( unitMarker.getBounds() ) map.removeLayer(unitMarker) } }, [linkName, markers, data]) // 原有useEffect逻辑不变,依赖项保持为data useEffect( () => { buildMap() addMarkers() renderMarkers() return () => { mapRef.current?.off(); mapRef.current?.remove(); } }, [ data, ] ); // 其余原有addMarkers、handleEditMarker、handleRemoveMarker逻辑不变 const addMarkers = ( editedMarkerLatLng ) => { const map = mapRef.current if ( addMarker ) { let marker; if ( isMarkerAdded === false ) { let onDrag = function ( e ) { let latlng = marker.getLatLng(); setCoordinates( { lats: latlng.lat, lngs: latlng.lng } ) setFormData( { ...formData, coordinates: [ latlng.lat, latlng.lng ] } ) marker.bindPopup().openPopup() }; let onClick = function ( e ) { setIsMarkerAdded( true ) map.off( 'click', onClick ); marker = L.marker( e.latlng, { icon: editMyIcon, draggable: true } ).addTo( map ).bindPopup( '<p>انا هنا يا جون</p>', { closePopupOnClick: false, autoClose: false, closeOnClick: false, } ).openPopup(); setCoordinates( { lats: e.latlng.lat, lngs: e.latlng.lng } ) setFormData( { ...setFormData, coordinates: [ e.latlng.lat, e.latlng.lng ] } ) marker.on( 'drag', onDrag ); setGetMarkerObject( marker ) }; map.on( 'click', onClick ); } } } const handleEditMarker = ( marker ) => { setIsMarkerAdded( true ) setEdit( true ) const lat = marker._latlng.lat const lng = marker._latlng.lng let editedMarker = data.find( item => item.markerPosition[ 0 ] === lat && item.markerPosition[ 1 ] === lng ) handleRemoveMarker( marker ) setFormData( { ...editedMarker, coordinates: [ lat, lng ] } ) marker = L.marker( [ lat, lng ], { icon: editMyIcon, draggable: true } ).addTo( mapRef.current ).bindPopup( '<p>انا هنا يا جون</p>', { closePopupOnClick: false, autoClose: false, closeOnClick: false, } ).openPopup(); let onDrag = function ( e ) { let latlng = marker.getLatLng(); setFormData( { ...editedMarker, coordinates: [ latlng.lat, latlng.lng ] } ) marker.bindPopup().openPopup() }; marker.on( 'drag', onDrag ); setGetMarkerObject( marker ) } const handleRemoveMarker = ( marker ) => { const lat = marker._latlng.lat const lng = marker._latlng.lng let tempMarkers = data.filter( item => item.markerPosition[ 0 ] !== lat && item.markerPosition[ 1 ] !== lng ) setMarkersList( tempMarkers ) } return ( <div className={ formCss ? 'map-container-form' : 'map-container' }> <div id="map"></div> { formCss && <p>coordinates: { `[ ${ coordinates.lats }, ${ coordinates.lngs } ]` }</p> } </div> ); }; export default MapContainer
修改完成后点击手风琴一次即可同时触发展开/折叠和地图定位逻辑。
内容的提问来源于stack exchange,提问作者mahmoudfathy557
相关产品推荐
相关产品推荐

