You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 15:09:00