[英]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
数组当它被填充到监听markers
的useEffect
时保持为空。
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.