gpt4 book ai didi

javascript - react 无限重渲染,useEffect问题,未触发设置状态

转载 作者:行者123 更新时间:2023-12-01 15:24:39 26 4
gpt4 key购买 nike

我的组件似乎进入了无休止的循环,我找不到原因。我正在使用useEffectuseState Hook ,但事实并非如此。 对于要将此标签为重复标签的任何人:请仔细阅读此问题。 这种无休止的重新渲染循环场景仅在以下情况下发生:

  • 鼠标移动太快
  • 用户单击开发人员控制台内的任何内容,然后触发mousemove事件(为了清晰起见,我省略了mousedown/mouseup处理程序,但它们与mousemove处理程序基本相同,触发对每个订阅的调用回调

  • 这是一个示例,但请注意,它可能导致无限循环并迫使您关闭浏览器选项卡
    代码沙箱: https://codesandbox.io/s/infiniteloopissue-lynhw?file=/index.js

    我正在使用 Map.forEach触发 setState事件上的所有订阅者的 mousemove。 (您可以在下面看到相关的代码),并且我正在使用 useEffect订阅/取消订阅此“更新事件”

    以下是“解决”该问题的一件事-检查“引用点”注释(在“鼠标使用方”中)。如果将回调作为 useCallback钩子(Hook)的依赖项删除,则一切正常。但这不是很好,因为在此示例中,我们显然只是将数据提取到状态中,但是 callback函数可能依赖于其他状态,在这种情况下它将不起作用。回调必须是可变的。

    我的猜测是,在 .forEach完成迭代之前, react 会以某种方式设法重新呈现,在这种情况下,它将取消订阅(因此删除键),然后重新订阅(因此再次添加键),触发另一个回调,然后触发该回调另一个取消订阅/重新订阅,我们进入循环。但这不应该对吗?我的意思是javascrip应该阻塞单线程,如何/为什么在forEach循环中间重新渲染 react ?

    此外,是否有人对如何“订阅” mousemove并运行回调有更好的主意。我最近在一些后端代码中看到了 EventEmitter,但是对此并不熟悉。我也不确定这是否可以解决问题,更新时的 react 优先于等待主线程完成 .forEach循环(至少我认为是这样)

    基本很简单:

    App.js
    const App = () => {
    return (
    <MouseProvider>
    <Component />
    </MouseProvider>
    )
    }

    Component.js
    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>
    )
    }

    该组件背后的想法是,始终记录鼠标在屏幕上的当前位置。该信息可以在上下文中轻松获得,但是为了在屏幕上呈现它,我们需要触发“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>
    )
    }

    因此,每次用户移动鼠标时, mousemove处理程序都会更新 static对象。提供程序组件本身 请勿重新呈现。

    鼠标消费
    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实现了两个函数,即 subscribeunsubscribeid放入以前在Provider中引用的回调Map中,这就是它所做的全部,它不调用回调,从不触发其他任何操作从Map对象添加/删除 callback
    所有的“更新”都是由 提供程序或更确切地说,是在每个 mousemove上回调提供程序回调的组件。换句话说, useEffect永远不会触发状态更新,鼠标交互不会触发状态更新。
    useEffect钩子(Hook)返回一个取消订阅的函数,该函数确保它“自行清理”,以便在卸下组件时不会触发 callback

    问题

    就像我说的那样,问题是,这个组件最终导致了一个无限的重新渲染循环,并且仅当鼠标移动得太快或鼠标移到“屏幕外”(例如进入开发人员控制台)时,才会发生这种情况。

    编辑:完全删除上下文,是没有必要的,并且引起困惑。
    EDIT2:添加了codesandbox

    最佳答案

    subscribeunsubscribe拆分为两个不同的useEffect调用:

    useEffect(() => {
    subscribe()
    }, [subscribe])



    useEffect(() => unsubscribe, [unsubscribe])

    这样可以防止删除和重新创建回调,但是如果卸载了该组件,它仍会进行清理。似乎确实有些不合时宜,但似乎确实可行。

    根据 react docs:

    The clean-up function runs before the component is removed from the UI to prevent memory leaks. Additionally, if a component renders multiple times (as they typically do), the previous effect is cleaned up before executing the next effect.



    (例如,当效果依赖项发生变化时)

    Edit sample sandbox

    关于javascript - react 无限重渲染,useEffect问题,未触发设置状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62199431/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com