[英]React infinite rerender, useEffect issue, no set state triggered
我的組件似乎進入了無限循環,我找不到原因。 我正在使用useEffect
和useState
掛鈎,但事實並非如此。 對於任何想要將其標記為重復的人:請仔細閱讀該問題。 這種無休止的重新渲染循環場景僅在以下情況下發生:
mousemove
事件(為了清楚起見,我省略了mousedown
/ mouseup
處理程序,但它們與mousemove
處理程序基本相同,觸發對每個訂閱回調的調用.這是一個示例,但請注意它可能會導致無限循環並強制您關閉瀏覽器選項卡Codesandbox:https://codesandbox.io/s/infiniteloopissue-lynhw?file=/index.js
我正在使用Map.forEach
在mousemove
事件上觸發任何訂閱者的setState
。 (您可以在下面看到相關代碼),我正在使用useEffect
訂閱/取消訂閱此“更新事件”
以下是“解決”問題的一件事 - 檢查參考點注釋(在鼠標使用者下方)。 如果回調作為useCallback
掛鈎的依賴項被刪除,則一切正常。 但這並不好,因為在這個例子中,我們顯然只是將數據提取到 state 中,但是callback
function 可能依賴於,比如其他一些 state,在這種情況下它不會起作用。 回調需要是可變的。
我的猜測是,反應以某種方式設法在.forEach
完成它的迭代之前重新渲染,在這種情況下,它會取消訂閱(從而刪除密鑰),並重新訂閱(從而再次添加它)觸發另一個回調,然后觸發另一個取消訂閱/重新訂閱,我們 go 進入一個循環。 但這應該是不可能的吧? 我的意思是 javascrip 應該阻塞單線程,如何/為什么在 forEach 循環中間重新渲染反應?
此外,是否有人對如何“訂閱” mousemove
移動並運行回調有更好的想法。 我最近在一些后端代碼中看到了一些EventEmitter
,但對它並不熟悉。 我也不確定這是否可以解決這里的問題,在更新時反應問題優先於等待主線程完成.forEach
循環(至少我認為是這樣)
基礎很簡單:
const App = () => {
return (
<MouseProvider>
<Component />
</MouseProvider>
)
}
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>
)
}
該組件背后的想法是,始終在屏幕上記下當前鼠標 position。 此信息可以在上下文中輕松獲得,但是為了在屏幕上呈現它,我們需要觸發“重新渲染”,因此 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>
)
}
因此,每次用戶移動鼠標時, mousemove
處理程序都會更新static
object。 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])
}
正如我們所見,Consumer Hook 實現了兩個函數,將id
subscribe
和unsubscribe
到回調的 Map 中,之前在 Provider 中引用過,這就是它所做的一切,它不調用回調,它永遠不會觸發其他任何東西從 Map object 添加/刪除callback
。 所有的“更新”都是由提供者完成的,或者更確切地說是回調提供者調用的組件,在每個mousemove
上。 換句話說, useEffect
不會觸發 state 更新,鼠標交互會。
useEffect
掛鈎返回一個取消訂閱 function 以確保它“自行清理”,以便在卸載組件時不會觸發callback
。
好吧,正如我所說的,問題是,我最終會使用這個組件進行無休止的重新渲染循環,並且只有當鼠標移動得太快或者它“離開屏幕”(例如進入開發人員控制台)時才會發生這種情況。
編輯:完全刪除上下文,沒有必要並且造成混亂。 EDIT2:添加了代碼框
拆分subscribe
和unsubscribe
兩個不同useEffect
調用:
useEffect(() => {
subscribe()
}, [subscribe])
和
useEffect(() => unsubscribe, [unsubscribe])
這可以防止回調被刪除和重新創建,但如果它卸載了組件,仍然會清理它。 它看起來確實有點笨拙,但它似乎確實有效。
根據反應文檔:
清理 function 在從 UI 中刪除組件之前運行,以防止 memory 泄漏。 此外,如果一個組件多次渲染(通常是這樣),則在執行下一個效果之前會清除上一個效果。
(例如當效果依賴改變時)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.