0


React报错之Too many re-renders

总览

产生"Too many re-renders. React limits the number of renders to prevent an infinite loop"错误有多方面的原因:

1.在一个组件的渲染方法中调用一个设置状态的函数。
2.立即调用一个事件处理器,而不是传递一个函数。
3.有一个无限设置与重渲染的

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 hereif (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 arrayreturn (<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 arrayreturn (<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 hereif (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 arrayconst 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 valueconst 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

钩子里面,以获得一个不会在不同渲染之间改变的记忆值。

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享


本文转载自: https://blog.csdn.net/Android_boom/article/details/128643608
版权归原作者 Web_boom 所有, 如有侵权,请联系我们删除。

“React报错之Too many re-renders”的评论:

还没有评论