【问题标题】:How can I access React state in my eventHandler?如何在我的 eventHandler 中访问 React 状态?
【发布时间】:2020-11-28 02:48:09
【问题描述】:

这是我的状态:

const [markers, setMarkers] = useState([])

我在 useEffect 挂钩中初始化 Leaflet 地图。它有一个click eventHandler。

useEffect(() => {
    map.current = Leaflet.map('mapid').setView([46.378333, 13.836667], 12)
    .
    .
    .
    map.current.on('click', onMapClick)
}, []

onMapClick 里面我在地图上创建了一个标记并将其添加到状态:

const onMapClick = useCallback((event) => {
    console.log('onMapClick markers', markers)
    const marker = Leaflet.marker(event.latlng, {
        draggable: true,
        icon: Leaflet.divIcon({
            html: markers.length + 1,
            className: 'marker-text',
        }),
    }).addTo(map.current).on('move', onMarkerMove)

    setMarkers((existingMarkers) => [ ...existingMarkers, marker])
}, [markers, onMarkerMove])

但我也想在这里访问markers 状态。但我无法在这里阅读markers。它始终是初始状态。我试图通过按钮的 onClick 处理程序调用onMapClick。在那里我可以阅读markers。如果原始事件从地图开始,为什么我不能阅读markers?如何读取onMapClick 中的状态变量?

这里是一个例子:https://codesandbox.io/s/jolly-mendel-r58zp?file=/src/map4.js 当您在地图中单击并查看控制台时,您会看到onMapClick 中的markers 数组保持为空,而它被填充到侦听markersuseEffect

【问题讨论】:

    标签: reactjs leaflet react-hooks react-leaflet use-state


    【解决方案1】:

    React 状态是异步的,它不会立即保证您会为您提供新状态,至于您的问题 如果原始事件从地图开始,为什么我不能读取标记异步性质以及函数基于其当前闭包和状态更新使用状态值的事实将反映在下一次重新渲染中,现有闭包不受影响但会创建新闭包,您不会在类组件上遇到这个问题因为你有 this 实例,它具有全局范围。

    作为一个开发组件,我们应该确保组件是从你调用它的地方控制的,而不是处理状态的函数闭包,它会在每次状态变化时重新渲染。您的解决方案是可行的,您应该在需要时将值传递给您传递给函数的任何事件或操作。

    编辑:- 它的简单只是将参数或 deps 传递给 useEffect 并将你的回调包装在里面,对于你的情况,它会是

    useEffect(() => {
        map.current = Leaflet.map('mapid').setView([46.378333, 13.836667], 12)
        .
        .
        .
        map.current.on('click',()=> onMapClick(markers)) //pass latest change
    }, [markers] // when your state changes it will call this again
    

    欲了解更多信息,请查看https://dmitripavlutin.com/react-hooks-stale-closures/,它将为您长期提供帮助!!!

    【讨论】:

    • 感谢您的回答。我知道 React 状态是异步的,但我真的不知道如何重构我的解决方案,以便 map 事件调用的 eventHandler 可以访问状态。
    【解决方案2】:

    很长,但您会明白为什么会发生这种情况以及更好的修复方法。闭包尤其是一个问题(也很难理解),主要是当我们设置 依赖于状态的单击处理程序时,如果具有新范围的处理程序函数没有重新附加到单击事件,然后闭包保持未更新,因此陈旧状态保留在点击处理函数中

    如果您在组件中完全理解它,useCallback 将返回对更新函数的新引用,即onMapClick 在其范围内包含更新的标记(状态),但是由于您仅在安装组件时在开始时设置“点击”处理程序,点击处理程序保持未更新,因为您已检查 if(! map.current),这会阻止任何新的处理程序附加到地图上。

    // in sandbox map.js line 40
      useEffect(() => {
        // this is the issue, only true when component is initialized
        if (! map.current) {
          map.current = Leaflet.map("mapid4").setView([46.378333, 13.836667], 12);
          Leaflet.tileLayer({ ....}).addTo(map.current);
          // we must update this since onMapClick was updated
          // but you're preventing this from happening using the if statement
          map.current.on("click", onMapClick);
        }
    
      }, [onMapClick]);
    

    现在我尝试将map.current.on("click", onMapClick); 移出if 块,但有一个问题,Leaflets 不是用新函数替换点击处理程序,而是adds another event handler(基本上是堆叠事件处理程序),所以我们必须删除在添加新的之前添加旧的,否则每次更新onMapClick 时我们最终都会添加多个处理程序。我们有 off() 函数。

    这是更新后的代码

    // in sandbox map.js line 40
    useEffect(() => {
      // this is the issue, only true when component is initialized
      if (!map.current) {
        map.current = Leaflet.map("mapid4").setView([46.378333, 13.836667], 12);
        Leaflet.tileLayer({ ....
        }).addTo(map.current);
      }
    
      // remove out of the condition block
      // remove any stale click handlers and add the updated onMapClick handler
      map.current.off('click').on("click", onMapClick);
    
    }, [onMapClick]);
    

    这是更新后的sandbox 的链接,它工作得很好。

    现在有另一个想法来解决它,而无需每次都替换点击处理程序。即一些全局变量,我认为这并不算太糟糕。

    为此添加globalMarkers 外部但在您的组件上方并每次更新它。

    let updatedMarkers = [];
    
    const Map4 = () => {
      let map = useRef(null);
      let path = useRef({});
      updatedMarkers =  markers; // update this variable each and every time with the new markers value
      ......
    
      const onMapClick = useCallback((event) => {
       console.log('onMapClick markers', markers)
       const marker = Leaflet.marker(event.latlng, {
          draggable: true,
          icon: Leaflet.divIcon({
            // use updatedMarkers here
            html: updatedMarkers.length + 1,
            className: 'marker-text',
          }),
        }).addTo(map.current).on('move', onMarkerMove)
    
        setMarkers((existingMarkers) => [ ...existingMarkers, marker])
      }, [markers, onMarkerMove])
     .....
    
    } // component end
    

    这个也很完美,用这个代码链接到sandbox。这个工作得更快。

    最后,上述将其作为参数传递的解决方案也可以!我更喜欢带有更新的if 块的那个,因为它很容易修改,并且您可以了解它背后的逻辑。

    【讨论】:

      猜你喜欢
      • 2015-03-08
      • 2020-11-05
      • 2016-11-14
      • 2020-10-07
      • 1970-01-01
      • 2018-11-13
      • 2017-12-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多