useEffect 中的无限循环

IT技术 reactjs react-hooks
2021-04-10 23:53:16

我一直在玩 React 16.7-alpha 中的新钩子系统,当我处理的状态是对象或数组时,我陷入了 useEffect 的无限循环。

首先,我使用 useState 并使用一个空对象启动它,如下所示:

const [obj, setObj] = useState({});

然后,在 useEffect 中,我再次使用 setObj 将其设置为空对象。作为第二个参数,我传递了 [obj],希望如果对象内容没有改变,它就不会更新但它一直在更新。我猜是因为无论内容如何,​​这些总是不同的对象让 React 认为它不断变化?

useEffect(() => {
  setIngredients({});
}, [ingredients]);

数组也是如此,但作为原语,它不会像预期的那样陷入循环。

使用这些新钩子,在检查天气内容是否发生变化时,我应该如何处理对象和数组?

6个回答

将空数组作为第二个参数传递给 useEffect 使其仅在挂载和卸载时运行,从而停止任何无限循环。

useEffect(() => {
  setIngredients({});
}, []);

我在https://www.robinwieruch.de/react-hooks/上关于 React hooks 的博客文章中澄清了这一点

请注意,如果您已指定,则卸载效果将运行清理功能。实际效果不会在卸载时运行。 reactjs.org/docs/hooks-effect.html#example-using-hooks-1
2021-05-25 23:53:16
这不能解决问题,您需要传递钩子使用的依赖项
2021-05-27 23:53:16
它实际上是一个空数组,而不是您应该传入的空对象。
2021-06-04 23:53:16
由于上面的人没有提供如何正确解决这个问题的链接或资源,我建议即将到来的duckduckgoers检查一下,因为它解决了我的无限循环问题:stackoverflow.com/questions/56657907/...
2021-06-07 23:53:16
正如 Dan Abramov 所说,当 setIngrediets 是一个依赖项时使用空数组是一种反模式。不要将 useEfetct 视为 componentDidMount() 方法
2021-06-15 23:53:16

有同样的问题。我不知道他们为什么不在文档中提及这一点。只想对 Tobias Haugen 的回答补充一点。

要在每个组件/父级重新渲染中运行,您需要使用:

  useEffect(() => {

    // don't know where it can be used :/
  })

在组件安装后运行一次(将呈现一次),您需要使用:

  useEffect(() => {

    // do anything only one time if you pass empty array []
    // keep in mind, that component will be rendered one time (with default values) before we get here
  }, [] )

在组件安装和 data/data2更改运行任何一次

  const [data, setData] = useState(false)
  const [data2, setData2] = useState('default value for first render')
  useEffect(() => {

// if you pass some variable, than component will rerender after component mount one time and second time if this(in my case data or data2) is changed
// if your data is object and you want to trigger this when property of object changed, clone object like this let clone = JSON.parse(JSON.stringify(data)), change it clone.prop = 2 and setData(clone).
// if you do like this 'data.prop=2' without cloning useEffect will not be triggered, because link to data object in momory doesn't changed, even if object changed (as i understand this)
  }, [data, data2] )

我大部分时间是如何使用它的:

export default function Book({id}) { 
  const [book, bookSet] = useState(false) 

  const loadBookFromServer = useCallback(async () => {
    let response = await fetch('api/book/' + id)
    response  = await response.json() 
    bookSet(response)
  }, [id]) // every time id changed, new book will be loaded

  useEffect(() => {
    loadBookFromServer()
  }, [loadBookFromServer]) // useEffect will run once and when id changes


  if (!book) return false //first render, when useEffect did't triggered yet we will return false

  return <div>{JSON.stringify(book)}</div>  
}
@endavid 取决于您使用的道具是什么
2021-05-23 23:53:16
@egdavid 那么当值更新时运行某个代码块的正确方法是什么?使用 UseEffect 会导致 lint 错误包含依赖项中的值,但这会导致无限循环,因此无法使用该方法
2021-05-27 23:53:16
这真的很有帮助,调用 useEffect 钩子中的异步函数仅在值更改时更新。谢了哥们
2021-06-03 23:53:16
根据 React 常见问题解答,从依赖项列表中省略函数是不安全的
2021-06-07 23:53:16
当然,如果您不使用组件范围内的任何值,那么省略它是安全的。但是从纯粹的 vue 设计/架构角度来看,这不是一个好的做法,因为如果您需要使用道具,它需要您将整个函数移动到效果内部,并且最终可能会使用 useEffect 方法,该方法将使用惊人的代码行数。
2021-06-13 23:53:16

我也遇到过同样的问题,我通过确保在第二个参数中传递原始值来修复它[]

如果你传递一个对象,React 将只存储对对象的引用并在引用改变时运行效果,这通常是每一次(但我现在不知道如何)。

解决方案是传递对象中的值。你可以试试,

const obj = { keyA: 'a', keyB: 'b' }

useEffect(() => {
  // do something
}, [Object.values(obj)]);

或者

const obj = { keyA: 'a', keyB: 'b' }

useEffect(() => {
  // do something
}, [obj.keyA, obj.keyB]);
是否将随机值传递给依赖数组?当然,这可以防止无限循环,但这是鼓励的吗?我也可以传递0给依赖数组?
2021-05-21 23:53:16
另一种方法是使用 useMemo 创建这样的值,这样引用被保留并且依赖项的值被评估为相同
2021-05-24 23:53:16
@Dinesh 如果您添加了扩展运算符}, ...[Object.values(obj)]); ... 或简单地删除方括号,则第一个解决方案将起作用 }, Object.values(obj)); ...
2021-05-24 23:53:16
@helado 您可以将 useMemo() 用于值或将 useCallback() 用于函数
2021-05-27 23:53:16

如果您正在构建自定义钩子,有时会导致无限循环,默认情况如下

function useMyBadHook(values = {}) {
    useEffect(()=> { 
           /* This runs every render, if values is undefined */
        },
        [values] 
    )
}

解决方法是使用相同的对象,而不是在每次函数调用时都创建一个新对象:

const defaultValues = {};
function useMyBadHook(values = defaultValues) {
    useEffect(()=> { 
           /* This runs on first call and when values change */
        },
        [values] 
    )
}

如果您在组件代码中遇到此问题,如果您使用 defaultProps 而不是 ES6 默认值,循环可能会得到修复

function MyComponent({values}) {
  useEffect(()=> { 
       /* do stuff*/
    },[values] 
  )
  return null; /* stuff */
}

MyComponent.defaultProps = {
  values = {}
}
谢谢!这对我来说并不明显,这正是我遇到的问题。
2021-06-19 23:53:16

正如文档(https://reactjs.org/docs/hooks-effect.htmluseEffect中所说,当您希望在每次渲染后执行某些代码时,可以使用钩子从文档:

useEffect 是否在每次渲染后运行?是的!

如果您想对此进行自定义,您可以按照同一页面稍后显示的说明进行操作 ( https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects )。基本上,该useEffect方法接受第二个参数,React 将检查该参数以确定是否必须再次触发效果。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

您可以将任何对象作为第二个参数传递。如果这个对象保持不变,你的效果只会在第一次坐骑后触发如果物体发生变化,效果会再次触发。