繁体   English   中英

如何在我的 eventHandler 中访问 React state?

[英]How can I access React state in my eventHandler?

这是我的 state:

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

我在 useEffect 挂钩中初始化了 Leaflet map。 它有一个click事件处理程序。

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

在该onMapClick中,我在 map 上创建了一个标记,并将其添加到 state:

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 state。 但我不能在这里阅读markers 它始终是最初的 state。 我试图通过按钮的 onClick 处理程序调用onMapClick 在那里我可以阅读markers 如果原始事件从 map 开始,为什么我不能读取markers 如何读取 onMapClick 中的onMapClick变量?

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

React state is asynchronous and it won't immediately guarantee you to give you the new state, as for your question Why can't I read markers if the original event starts at the map its an asynchronous nature and the fact that state values are used通过基于其当前闭包的函数和 state 更新将反映在下一次重新渲染中,现有闭包不受影响但会创建新闭包,您不会在 class 组件上遇到此问题,因为您有实例,其中有全球 scope。

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

编辑:- 它的简单只是将参数或 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/ ,它将为您长期提供帮助!

很长,但您会理解为什么会发生这种情况以及更好的修复方法。 Closures are especially an issue (also hard to understand), mostly when we set click handlers which are dependent on the state , if the handler function with the new scope is not re-attached to the click event, then closures remain un-updated and因此陈旧的 state 保留在点击处理程序 function 中

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

// 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 不是用新的 function 替换点击处理程序,而是添加了另一个事件处理程序(基本上是堆叠事件处理程序),所以我们必须在添加新的之前删除旧的,否则我们每次更新onMapClick时都会添加多个处理程序。 为此,我们有off() function。

这是更新的代码

// 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]);

这是更新的沙盒的链接,它工作得很好。

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

为此,将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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM