太多的重新渲染。React限制渲染次数以防止无限循环
Too many re-renders. React limits the number of renders to prevent an infinite loop
出现“重新渲染次数太多。React 限制渲染次数以防止无限循环”的错误有多种原因:
- 调用一个在组件的 render 方法中设置状态的函数。
- 立即调用事件处理程序,而不是传递函数。
- 有一个
useEffect
可以无限设置和重新渲染的钩子。
立即调用事件处理程序
以下是错误发生方式的示例:
import {useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); // ⛔️ Too many re-renders. React limits the number // of renders to prevent an infinite loop. return ( <div> <button onClick={setCounter(counter + 1)}>Increment</button> <h1>Count: {counter}</h1> </div> ); }
问题是我们立即调用事件处理setCounter
程序中的函数
onClick
。
要解决该错误,
请将函数传递给事件处理程序onClick
,而不是调用函数的结果。
import {useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); return ( <div> <button onClick={() => setCounter(counter + 1)}>Increment</button> <h1>Count: {counter}</h1> </div> ); }
现在我们将一个函数传递给事件处理程序,而不是
setCounter
在页面加载时调用该方法。
setState
则会触发一个操作,并且组件会无限地重新呈现。在函数组件体内设置状态
如果我们尝试立即设置组件的状态而不使用条件或事件处理程序,也会发生该错误。
import {useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); // ⛔️ Too many re-renders. React limits the number // of renders to prevent an infinite loop. setCounter(counter + 1); return ( <div> <h1>Count: {counter}</h1> </div> ); }
问题是,setCounter
当组件渲染并更新状态时,该函数会被调用,这会导致重新渲染并无限地执行此操作。
使用useEffect
钩子只设置状态一次
解决该错误的一种方法是在挂钩中调用该setState
函数useEffect
。
import {useState, useEffect} from 'react'; export default function App() { const [counter, setCounter] = useState(); useEffect(() => { setCounter(100); }, []); return ( <div> <h1>Count: {counter}</h1> </div> ); }
请注意,我们为钩子的依赖项使用了一个空数组。
该useEffect
钩子仅在组件安装后运行,因此状态仅设置一次。
给useState()
钩子传递一个初始值
您还可以通过将初始值或函数传递给
useState()挂钩来初始化状态来解决该错误。
import {useState} from 'react'; export default function App() { const [counter, setCounter] = useState(() => 100 + 100); return ( <div> <h1>Count: {counter}</h1> </div> ); }
我们将一个函数传递给该useState
方法。该函数只会在组件第一次渲染时被调用,并计算初始状态。您还可以将初始值直接传递给该useState
方法。
我写了一篇关于
为 useState 设置条件初始值的详细指南。
使用条件或事件处理程序来更新状态
或者,您可以像前面的示例一样使用条件或事件处理程序。
import {useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); // 👇️ your condition here if (Math.random() > 0.5) { setCounter(counter + 1); } return ( <div> <h1>Count: {counter}</h1> </div> ); }
truthy
因为这会导致无限的重新渲染循环。 useEffect
解决使用hook时出现的错误
当使用具有导致无限重新渲染的依赖项的useEffect方法时,也会发生该错误
。
import {useEffect, useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); useEffect(() => { // ⛔️ Too many re-renders. React limits the number // of renders to prevent an infinite loop. setCounter(counter + 1); }); // 👈️ forgot to pass dependency array return ( <div> <h1>Count: {counter}</h1> </div> ); }
问题是我们没有将依赖项数组传递给挂钩useEffect
。
这意味着该钩子在每次渲染时运行,它更新组件的状态,然后无限地再次运行。
解决该错误的一种方法是提供一个空数组作为 的第二个参数useEffect
。
import {useEffect, useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); useEffect(() => { setCounter(counter + 1); }, []); // 👈️ empty dependencies array return ( <div> <h1>Count: {counter}</h1> </div> ); }
useEffect
1
无论App
组件是否重新渲染,代码都会将计数器增加到并不再运行。
如果您收到警告“React Hook useEffect 缺少依赖项”,请查看以下文章。
寻找防止无限重新渲染的条件
如果您必须指定无限地重新渲染组件的依赖项,请尝试寻找阻止这种情况的条件。
import {useEffect, useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); useEffect(() => { // 👇️ some condition here if (Math.random() > 0.5) { setCounter(counter + 1); } }, [counter]); return ( <div> <h1>Count: {counter}</h1> </div> ); }
不要使用变化的对象或数组作为useEffect
依赖项
确保您没有使用每次渲染时都不同的对象或数组作为钩子的依赖项useEffect
。
import {useEffect, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); const obj = {country: 'Chile', city: 'Santiago'}; useEffect(() => { // ⛔️ Too many re-renders. React limits the number // of renders to prevent an infinite loop. setAddress(obj); console.log('useEffect called'); }, [obj]); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
问题在于 JavaScript 中的对象是通过引用进行比较的。该obj
变量在每次渲染时存储具有相同键值对但不同引用(内存中不同位置)的对象。
将对象移动到钩子useEffect
内
解决错误的一种方法是将对象移动到挂钩内useEffect
,这样我们就可以将其从依赖项数组中删除。
import {useEffect, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); useEffect(() => { // 👇️ move object inside of useEffect // and remove it from dependencies array const obj = {country: 'Chile', city: 'Santiago'}; setAddress(obj); console.log('useEffect called'); }, []); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
将对象的属性传递给依赖数组
另一种解决方案是将对象的属性传递给依赖项数组。
import {useEffect, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); const obj = {country: 'Chile', city: 'Santiago'}; useEffect(() => { setAddress({country: obj.country, city: obj.city}); console.log('useEffect called'); // 👇️ object properties instead of the object itself }, [obj.country, obj.city]); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
现在 React 不是在测试一个对象是否改变了,它是在测试
obj.country
和obj.city
字符串在渲染之间是否改变了。
使用useMemo
钩子来记忆对象或数组
或者,我们可以使用
useMemo钩子来获取在渲染之间不会改变的记忆值。
import {useEffect, useMemo, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); // 👇️ get memoized value const obj = useMemo(() => { return {country: 'Chile', city: 'Santiago'}; }, []); useEffect(() => { setAddress(obj); console.log('useEffect called'); }, [obj]); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
useMemo
,以获得一个在渲染之间不会改变的记忆值。我们传递给钩子的第二个参数useMemo
是一个 dependencies 数组,它确定我们传递给的回调函数何时useMemo
重新运行。
数组也通过引用进行比较
请注意,JavaScript 中也通过引用来比较数组。因此,具有相同值的数组也可能导致您的useEffect
钩子被无限次触发。
import {useEffect, useMemo, useState} from 'react'; export default function App() { const [nums, setNums] = useState([1, 2, 3]); const arr = [4, 5, 6]; useEffect(() => { // ⛔️ Too many re-renders. React limits the number // of renders to prevent an infinite loop. setNums(arr); console.log('useEffect called'); }, [arr]); return <div>{nums[0]}</div>; }
该数组在重新渲染之间存储相同的值,但指向内存中的不同位置,并且每次重新渲染组件时都有不同的引用。
我们用于对象的相同方法在处理数组时也是有效的。例如,我们可以使用useMemo
钩子来获取在渲染之间不会改变的记忆值。
import {useEffect, useMemo, useState} from 'react'; export default function App() { const [nums, setNums] = useState([1, 2, 3]); const arr = useMemo(() => { return [4, 5, 6]; }, []); useEffect(() => { setNums(arr); console.log('useEffect called'); }, [arr]); return <div>{nums[0]}</div>; }
我们将数组的初始化包装在钩子内部useMemo
,以获取在渲染之间不会改变的记忆值。
额外资源
您可以通过查看以下教程来了解有关相关主题的更多信息: