我一直在使用React 16.7-alpha中的新钩子系统,并且当我正在处理的状态是对象或数组时陷入useEffect的无限循环中。
首先,我使用useState并使用一个空对象将其初始化:
const [obj, setObj] = useState({});
然后,在useEffect中,我使用setObj再次将其设置为空对象。作为第二个参数,我传递[obj],希望如果对象的 content 不变,它不会更新。但是它一直在更新。我想是因为不管内容如何,这些总是不同的对象,这使React认为它不断变化?
useEffect(() => {
setIngredients({});
}, [ingredients]);
对于数组也是如此,但作为基元,它不会像预期的那样卡在循环中。
使用这些新的挂钩,在检查天气内容是否已更改时应该如何处理对象和数组?
答案 0 :(得分:22)
将空数组作为第二个参数传递给useEffect使其只能在安装和卸载时运行,从而停止任何无限循环。
with open('export_log.csv', 'w') as fd:
output = csv.writer(fd)
…
上有关React钩子的博客文章中向我阐明了这一点。
答案 1 :(得分:20)
有同样的问题。我不知道他们为什么不在文档中提及这一点。只是想在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
}, [] )
要在组件安装和数据/ 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)
useEffect(() => {
loadBookFromServer()
}, [id]) // every time id changed, new book will be loaded
async function loadBookFromServer() {
let response = await fetch('api/book/' + id)
response = await response.json()
bookSet(response)
}
if (!book) return false //first render, when useEffect did't triggered yet we will return false
return <div>{JSON.stringify(book)}</div>
}
答案 2 :(得分:6)
如果您在 useEffect 的末尾包含空数组:
useEffect(()=>{
setText(text);
},[])
它将运行一次。
如果在数组上还包含参数:
useEffect(()=>{
setText(text);
},[text])
只要文本参数更改,它就会运行。
答案 3 :(得分:4)
如文档(https://reactjs.org/docs/hooks-effect.html中所述),{strong}当您希望在每次渲染后执行一些代码时使用useEffect
钩子。从文档中:
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
您可以传递任何对象作为第二个参数。 如果此对象保持不变,则只会在首次安装后触发您的效果。如果对象发生更改,则会再次触发效果。
答案 4 :(得分:3)
我也遇到了同样的问题,并通过确保在第二个参数[]
中传递原始值来解决了这个问题。
如果您传递一个对象,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]);
答案 5 :(得分:2)
如果使用此优化,请确保数组包含组件作用域中所有随时间变化并被效果使用的值(例如props和state)。
我相信他们正在尝试表达使用陈旧数据的可能性,并意识到这一点。只要我们知道如果这些值中的任何一个发生更改,它将执行效果,我们在array
中为第二个参数发送的值的类型无关紧要。如果我们将ingredients
用作效果内计算的一部分,则应将其包括在array
中。
const [ingredients, setIngredients] = useState({});
// This will be an infinite loop, because by shallow comparison ingredients !== {}
useEffect(() => {
setIngredients({});
}, [ingredients]);
// If we need to update ingredients then we need to manually confirm
// that it is actually different by deep comparison.
useEffect(() => {
if (is(<similar_object>, ingredients) {
return;
}
setIngredients(<similar_object>);
}, [ingredients]);
答案 6 :(得分:2)
如果要构建自定义钩子,有时会导致无限循环,默认情况如下所示
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]
)
}
答案 7 :(得分:2)
主要问题是 useEffect 将传入值与当前值 shallowly 进行比较。这意味着使用“===”比较来比较这两个值,该比较仅检查对象引用,尽管数组和对象值相同,但将它们视为两个不同的对象。我建议您查看我关于 useEffect 作为生命周期方法的article。
答案 8 :(得分:1)
万一您需要比较对象,并且在更新对象时,这里是一个deepCompare
钩子用于比较。接受的答案肯定不会解决该问题。如果您需要效果在挂载时仅运行一次,那么拥有[]
数组是合适的。
此外,其他投票答案仅通过执行obj.value
或类似的操作来解决对原始类型的检查,首先达到未嵌套的级别。对于深度嵌套的对象,这可能不是最好的情况。
因此,这是一种在所有情况下均适用的软件。
import { DependencyList } from "react";
const useDeepCompare = (
value: DependencyList | undefined
): DependencyList | undefined => {
const ref = useRef<DependencyList | undefined>();
if (!isEqual(ref.current, value)) {
ref.current = value;
}
return ref.current;
};
您可以在useEffect
钩中使用相同的
React.useEffect(() => {
setState(state);
}, useDeepCompare([state]));
答案 9 :(得分:0)
我不确定这是否对您有用,但是您可以尝试添加.length,如下所示:
useEffect(() => {
// fetch from server and set as obj
}, [obj.length]);
就我而言(我正在获取一个数组!),它在安装时获取数据,然后仅在更改时才获取数据,并且没有进入循环。
答案 10 :(得分:0)
您的无限循环是由于圆度
useEffect(() => {
setIngredients({});
}, [ingredients]);
setIngredients({});
将更改ingredients
的值(每次将返回一个新引用),该值将运行setIngredients({})
。要解决此问题,您可以使用以下任何一种方法:
const timeToChangeIngrediants = .....
useEffect(() => {
setIngredients({});
}, [timeToChangeIngrediants ]);
setIngrediants
将在timeToChangeIngrediants
更改后运行。
Object.values(ingrediants)
作为第二个参数传递给useEffect。 useEffect(() => {
setIngredients({});
}, Object.values(ingrediants));
答案 11 :(得分:0)
最好的方法是使用 Lodash 中的 usePrevious()和 _。isEqual()将以前的值与当前值进行比较。 导入 isEqual 和 useRef 。将您以前的值与 useEffect()中的当前值进行比较。如果它们相同,则不进行其他任何更新。 usePrevious(value)是一个自定义挂钩,可使用 useRef()创建 ref 。
下面是我的代码段。我遇到了使用Firebase钩子更新数据的无限循环问题
import React, { useState, useEffect, useRef } from 'react'
import 'firebase/database'
import { Redirect } from 'react-router-dom'
import { isEqual } from 'lodash'
import {
useUserStatistics
} from '../../hooks/firebase-hooks'
export function TMDPage({ match, history, location }) {
const usePrevious = value => {
const ref = useRef()
useEffect(() => {
ref.current = value
})
return ref.current
}
const userId = match.params ? match.params.id : ''
const teamId = location.state ? location.state.teamId : ''
const [userStatistics] = useUserStatistics(userId, teamId)
const previousUserStatistics = usePrevious(userStatistics)
useEffect(() => {
if (
!isEqual(userStatistics, previousUserStatistics)
) {
doSomething()
}
})
答案 12 :(得分:0)
您还可以在依赖项数组中分解对象,这意味着状态仅在对象的某些部分更新时才会更新。
就本例而言,假设成分中包含胡萝卜,我们可以将其传递给依赖项,并且只有在胡萝卜发生变化时,状态才会更新。
然后您可以进一步执行此操作,仅在某些点更新胡萝卜的数量,从而控制状态何时更新并避免无限循环。
useEffect(() => {
setIngredients({});
}, [ingredients.carrots]);
当用户登录网站时,可以使用这种方式的示例。当他们登录时,我们可以对用户对象进行解构以提取其cookie和权限角色,并相应地更新应用程序的状态。
答案 13 :(得分:0)
我的Case在遇到无限循环时很特别,感觉就像这样:
我有一个对象,可以说来自道具的objX,我正在像这样的道具中对其进行销毁:
const { something: { somePropery } } = ObjX
并且我使用somePropery
作为对useEffect
的依赖项,例如:
useEffect(() => {
// ...
}, [somePropery])
这导致了一个无限循环,我试图通过将整个something
作为依赖项来处理此问题,并且它工作正常。
答案 14 :(得分:0)
我用于数组状态的另一个有效解决方案是:
useEffect(() => {
setIngredients(ingredients.length ? ingredients : null);
}, [ingredients]);