React报错Too many re-renders解决
作者:chuck
总览
产生"Too many re-renders. React limits the number of renders to prevent an infinite loop"错误有多方面的原因:
- 在一个组件的渲染方法中调用一个设置状态的函数。
- 立即调用一个事件处理器,而不是传递一个函数。
- 有一个无限设置与重渲染的
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> ); }
上述代码问题在于,我们在onClick
事件处理器中立即调用了setCounter
函数。
该函数是在页面加载时立即被调用,而不是事件触发后调用。
传递函数
为了解决该错误,为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
函数在组件渲染时被调用、更新状态,并导致重新渲染,而且是无限重新渲染。
你可以通过向useState()
钩子传递一个初始值或一个函数来初始化状态,从而解决这个错误。
import {useState} from 'react'; export default function App() { const [counter, setCounter] = useState(() => 100 + 100); return ( <div> <h1>Count: {counter}</h1> </div> ); }
我们向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> ); }
如果你像上面的例子那样使用一个条件,请确保该条件不总是返回一个真值,因为这将导致无限的重新渲染循环。
"Too many re-renders. React limits the number of renders to prevent an infinite loop"错误也会在使用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
组件是否被重新渲染。
如果你必须指定一个依赖来无限地重新渲染你的组件,试着寻找一个可以防止这种情况的条件。
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
钩子的依赖。
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
钩子里面,这样我们就可以把它从依赖数组中移除。
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
钩子来获得一个在不同渲染之间不会改变的记忆值。
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
钩子的第二个参数是一个依赖数组,它决定了我们传递给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
钩子里面,以获得一个不会在不同渲染之间改变的记忆值。
以上就是React报错Too many re-renders解决的详细内容,更多关于React报错Too many re-renders的资料请关注脚本之家其它相关文章!
您可能感兴趣的文章:
- React报错Type '() => JSX.Element[]' is not assignable to type FunctionComponent
- React报错Element type is invalid解决案例
- React报错Function components cannot have string refs
- 解决React报错Property 'X' does not exist on type 'HTMLElement'
- Can't perform a React state update on an unmounted component报错解决
- Objects are not valid as a React child报错解决
- 解决React报错No duplicate props allowed
- 解决React报错React.Children.only expected to receive single React element child