Del*_*ium 6 javascript reactjs
我的组件似乎进入了无限循环,我找不到原因。我正在使用useEffect和useState挂钩,但事实并非如此。对于想要将其标记为重复的任何人:请仔细阅读问题。这种无休止的重新渲染循环场景仅在以下情况下发生:
mousemove在之后触发事件(为了清楚起见,我省略了mousedown/mouseup处理程序,但它们本质上与mousemove处理程序相同,触发对每个订阅的回调的调用。这是一个示例,但请注意它可能会导致无限循环并强制您关闭浏览器选项卡 Codesandbox: https ://codesandbox.io/s/infiniteloopissue-lynhw ? file =/ index.js
我正在使用在事件上Map.forEach触发setState任何订阅者的mousemove。(您可以在下面看到相关代码),我正在使用useEffect此“更新事件”订阅/取消订阅
“修复”问题的一件事如下 - 检查参考点评论(在鼠标使用者下方)。如果回调作为useCallback钩子的依赖项被删除,则一切正常。但这并不好,因为在这个例子中,我们显然只是将数据提取到状态中,但该callback函数可能依赖于其他状态,在这种情况下它不起作用。回调需要是可变的。
我的猜测是反应以某种方式设法在.forEach完成它的迭代之前重新渲染,在这种情况下它会取消订阅(从而删除密钥),然后重新订阅(从而再次添加它)触发另一个回调,然后触发另一个回调取消订阅/重新订阅,我们进入一个循环。但这应该不可能吧?我的意思是 javascript 假设阻塞单线程,如何/为什么在 forEach 循环中间重新渲染?
另外,是否有人对如何“订阅” amousemove并运行回调有更好的想法。最近EventEmitter在一些后端代码中看到了一些,但不是很熟悉。我也不确定这是否可以解决这里的问题,更新时反应的问题优先于等待主线程完成.forEach循环(至少我认为是这样)
基础很简单:
const App = () => {
return (
<MouseProvider>
<Component />
</MouseProvider>
)
}
Run Code Online (Sandbox Code Playgroud)
const Component = props => {
const [mouse, setMouse] = useState({})
const callback = data => {
setMouse({ x: data.x, y: data.y })
}
useMouseTracker(callback)
return (
<div>
{`x: ${mouse.x} y: ${mouse.y}`}
</div>
)
}
Run Code Online (Sandbox Code Playgroud)
该组件背后的想法是,始终在屏幕上写下当前的鼠标位置。这些信息在上下文中很容易获得,但是为了在屏幕上呈现它,我们需要触发“ReRender”,所以 Context API 不是一个解决方案。
// Static mutable object used.
const mouseData = { x: 0, y: 0 }
// A map of "id : callback" pairs
const subscribers = new Map()
Provider = ({ children }) => {
const mouseMoveHandler = useCallback(event => {
if (event) {
mouseData.x = event.clientX
mouseData.y = event.clientY
subscribers.forEach(callback => {
callback({ ...mouseData})
})
}
}, [])
useEffect(() => {
window.addEventListener('mousemove', mouseMoveHandler)
return () => {
window.removeEventListener('mousemove', mouseMoveHandler)
}
}, [mouseMoveHandler])
return (
<React.Fragment>
{children}
</React.Fragment>
)
}
Run Code Online (Sandbox Code Playgroud)
因此,每次用户移动鼠标时,mousemove处理程序都会更新static对象。Provider 组件本身不会重新渲染。
useMouseTracker = callback => {
const id = 0 // This value is not 0, it's a system-wide per-component constant, guaranteed, tried and tested
const subscribe = useCallback(() => {
subscribers.set(id, callback)
}, [id, callback /* Reference Point */])
const unsubscribe = useCallback(() => {
subscribers.delete(id)
}, [id])
useEffect(() => {
subscribe()
return unsubscribe
}, [subscribe, unsubscribe])
}
Run Code Online (Sandbox Code Playgroud)
正如我们所看到的,消费者钩实现两个功能,subscribe并unsubscribe在id进入地图回调,以前在提供参考,也就是它的作用,它不调用回调函数,它永远不会触发任何其他然后添加/删除在callback从Map对象。所有的“更新”都是由提供者完成的,或者更确切地说是回调提供者调用的组件,在每个mousemove. 换句话说,useEffect鼠标交互不会触发状态更新。
该useEffect钩子返回一个取消订阅函数,以确保它“自行清理”,以便callback在组件被卸载时不会被触发。
正如我所说的问题是,我最终会使用此组件进行无休止的重新渲染循环,并且仅在鼠标移动太快或“屏幕外”(例如进入开发人员控制台)时才会发生这种情况。
编辑:完全删除了上下文,没有必要并且导致混乱。EDIT2:添加代码和框
subscribe将和拆分unsubscribe为两个不同的useEffect调用:
useEffect(() => {
subscribe()
}, [subscribe])
Run Code Online (Sandbox Code Playgroud)
和
useEffect(() => unsubscribe, [unsubscribe])
Run Code Online (Sandbox Code Playgroud)
这可以防止回调被删除和重新创建,但如果卸载了组件,仍然会清理。这看起来确实有点笨拙,但似乎确实有效。
根据反应文档:
清理函数会在组件从 UI 中删除之前运行,以防止内存泄漏。此外,如果一个组件渲染多次(通常会这样做),则在执行下一个效果之前会清除前一个效果。
(例如,当效果依赖性发生变化时)
| 归档时间: |
|
| 查看次数: |
372 次 |
| 最近记录: |