【问题标题】:Gatsby: Context update causes infinite render loop盖茨比:上下文更新导致无限渲染循环
【发布时间】:2019-09-30 22:05:46
【问题描述】:

我正在尝试在 Gatsby 页面加载后更新上下文。

按照我的做法,为所有页面提供上下文,一旦页面加载,上下文就会更新(使用useEffect 完成以确保它仅在组件挂载时发生)。

不幸的是,这会导致无限渲染循环(在 Firefox 中可能不会,但至少在 Chrome 中)。

为什么会这样?我的意思是,上下文更新意味着提供程序下面的所有组件都被重新渲染,但是 useEffect 应该只运行一次,并且那是组件挂载的时候。

这里是代码https://codesandbox.io/s/6l3337447n

当您转到第二页时会发生无限循环(第一页底部的链接)。

如果我想在页面加载时更新上下文,这里的解决方案是什么?

【问题讨论】:

    标签: javascript reactjs gatsby


    【解决方案1】:

    此问题的正确答案不是将空依赖数组传递给useEffect,而是将上下文的mergeData 包装在useCallback hook 中。我无法编辑您的代码,但您可能还需要将依赖项添加到您的 useCallback,如下面的示例所示

    import React, { useState, useCallback } from "react"
    
    const defaultContextValue = {
      data: {
        // set initial data shape here
        menuOpen: false,
      },
      mergeData: () => {},
    }
    
    const Context = React.createContext(defaultContextValue)
    const { Provider } = Context
    
    function ContextProviderComponent({ children }) {
      const [data, setData] = useState({
        ...defaultContextValue,
        mergeData, // shorthand method name
      })
    
      const mergeData = useCallback((newData) {
        setData(oldData => ({
          ...oldData,
          data: {
            ...oldData.data,
            ...newData,
          },
        }))
      }, [setData])
    
      return <Provider value={data}>{children}</Provider>
    }
    
    export { Context as default, ContextProviderComponent }
    

    选择的答案不正确,因为反应文档明确表示不要省略当前选择的答案所暗示的效果中使用的依赖项。

    如果您将es-linteslint-plugin-react-hooks 一起使用,它会告诉您这是不正确的。

    注意

    如果您使用此优化,请确保数组包含所有值 从改变的组件范围(例如道具和状态) 时间和效果使用的时间。否则,您的代码将 从以前的渲染中引用过时的值。了解更多关于如何 处理函数以及当数组变化太频繁时该怎么办。

    https://reactjs.org/docs/hooks-effect.html

    从依赖项列表中省略函数是否安全?一般来说 说,没有。很难记住使用了哪些道具或状态 通过效果之外的功能。这就是为什么通常你会想要 声明其内部效果所需的功能。然后很容易 查看效果依赖的组件范围中的哪些值:

    https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies

    【讨论】:

      【解决方案2】:

      默认情况下,useEffect 运行每个渲染。在您的示例中,useEffect 每次渲染都会更新 context,从而触发无限循环。

      React doc: 中有这个位

      如果你想运行一个效果并且只清理一次(在挂载和卸载时),你可以传递一个空数组([])作为第二个参数。这告诉 React 你的效果不依赖于任何来自 props 或 state 的值,所以它永远不需要重新运行。这不是作为特殊情况处理的——它直接遵循依赖项数组的工作方式。

      因此适用于您的示例:

        useEffect(() => {
          console.log("CONTEXT DATA WHEN PAGE 2 LOADS:", data)
          mergeData({
            location,
          })
      -  }, [location, mergeData, data])
      +  }, [])
      

      这样,useEffect 仅在第一次挂载时运行。我认为您也可以将location 留在其中,它还可以防止无限循环,因为useEffect 不依赖于来自context 的值。

      【讨论】:

      • 谢谢你,德里克!知道为什么在指定[location, mergeData, data] 时它会更新吗?我的意思是,mergeData 是一个不会改变的函数。 location 不应在重新渲染时更改,因为路线保持不变。同样,data 设置为location,因此也不应更改。最后,mergeData 只是一个绝对不会改变的函数。 PS:我欠你对上一篇文章的跟进,但我需要先解决一些其他紧迫的问题..
      • 有趣,我想我找到了上述问题的答案。是变量data 导致useEffect 在重新渲染时触发。 locationmergeData 都保持不变。这可能会回到这样一个事实,即我在context.js 中使用的spread 语法实际上将location 对象的克隆 合并到上下文data 对象中。因此,在每次上下文更新时,data 都是一个新对象。啊哈,一定是这样的!来源spreaddeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
      • @Magnus 抱歉回复晚了,我想你就在那儿!
      • 这是不正确的。在这里查看exhaustive-deps
      • @TemporaryFix 啊谢谢!我忘记了问题中的问题,但认为我的回答没有说​​明任何问题。感谢您的澄清,希望 OP 可以看到并将您的答案标记为正确。
      猜你喜欢
      • 2022-10-23
      • 1970-01-01
      • 1970-01-01
      • 2019-06-10
      • 2010-12-24
      • 2020-07-26
      • 1970-01-01
      • 2020-09-06
      • 1970-01-01
      相关资源
      最近更新 更多