我一直在使用React 16.7-alpha中的新钩子系统,当我正在处理的状态是一个对象或数组时,它会在useEffect中陷入无限循环。
首先,我使用useState并使用如下的空对象启动它:
const [obj, setObj] = useState({});
然后,在useEffect中,我使用setObj将其再次设置为空对象。作为第二个参数,我传递[obj],希望如果对象的内容没有改变,它不会更新。但它不断更新。我想因为无论内容如何,这些都是不同的对象,让React认为它在不断变化?
useEffect(() => {
setIngredients({});
}, [ingredients]);
数组也是如此,但作为一个原语,它不会像预期的那样卡在循环中。
使用这些新的钩子,在检查天气内容是否发生变化时,我应该如何处理对象和数组?
将空数组作为useEffect的第二个参数传递使其仅在mount和unmount上运行,从而停止任何无限循环。
useEffect(() => {
setIngredients({});
}, []);
在https://www.robinwieruch.de/react-hooks/的React hooks博客文章中向我澄清了这一点
有同样的问题。我不知道他们为什么不在文档中提到这一点。只想添加一点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
}, [] )
要在组件装载和数据/数据2上运行任何一次更改:
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>
}
如文档(https://reactjs.org/docs/hooks-effect.html)中所述,当您希望在每次渲染之后执行某些代码时,可以使用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
您可以将任何对象作为第二个参数传递。如果此对象保持不变,则只有在第一次安装后才会触发您的效果。如果对象发生变化,则会再次触发效果。
我也遇到了同样的问题,我通过确保在第二个参数[]
中传递原始值来修复它。
如果你传递一个对象,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]);
我不确定这是否适合你,但你可以尝试添加.length这样:
useEffect(() => {
// fetch from server and set as obj
}, [obj.length]);
在我的情况下(我正在获取数组!)它在mount上获取数据,然后仅在更改时再次获取并且它不会进入循环。
你的无限循环是由于圆度
useEffect(() => {
setIngredients({});
}, [ingredients]);
setIngredients({});
将改变ingredients
的值(每次都会返回一个新的引用),这将运行setIngredients({})
。要解决此问题,您可以使用以下任一方法
const timeToChangeIngrediants = .....
useEffect(() => {
setIngredients({});
}, [timeToChangeIngrediants ]);
当setIngrediants
改变时,timeToChangeIngrediants
将会运行。
Object.values(ingrediants)
作为useEffect的第二个参数传递。useEffect(() => {
setIngredients({});
}, Object.values(ingrediants));