太多的重新渲染。React限制渲染次数以防止无限循环

太多的重新渲染。React限制渲染次数以防止无限循环

Too many re-renders. React limits the number of renders to prevent an infinite loop

出现“重新渲染次数太多。React 限制渲染次数以防止无限循环”的错误有多种原因:

  1. 调用一个在组件的 render 方法中设置状态的函数。
  2. 立即调用事件处理程序,而不是传递函数。
  3. 有一个useEffect可以无限设置和重新渲染的钩子。

太多的重新渲染反应限制了数量

立即调用事件处理程序

以下是错误发生方式的示例:

应用程序.js
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,而不是调用函数的结果。

应用程序.js
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则会触发一个操作,并且组件会无限地重新呈现。

在函数组件体内设置状态

如果我们尝试立即设置组件的状态而不使用条件或事件处理程序,也会发生该错误。

应用程序.js
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

索引.js
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()挂钩来初始化状态来解决该错误。

应用程序.js
import {useState} from 'react'; export default function App() { const [counter, setCounter] = useState(() => 100 + 100); return ( <div> <h1>Count: {counter}</h1> </div> ); }

我们将一个函数传递给该useState方法。该函数只会在组件第一次渲染时被调用,并计算初始状态。您还可以将初始值直接传递给该useState方法。

我写了一篇关于
为 useState 设置条件初始值的详细指南。

使用条件或事件处理程序来更新状态

或者,您可以像前面的示例一样使用条件或事件处理程序。

应用程序.js
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方法时,也会发生该错误

应用程序.js
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

应用程序.js
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 缺少依赖项”,请查看以下文章

寻找防止无限重新渲染的条件

如果您必须指定无限地重新渲染组件的依赖项,请尝试寻找阻止这种情况的条件。

应用程序.js
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

应用程序.js
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,这样我们就可以将其从依赖项数组中删除。

应用程序.js
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> ); }

将对象的属性传递给依赖数组

另一种解决方案是将对象的属性传递给依赖项数组。

应用程序.js
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.countryobj.city字符串在渲染之间是否改变了。

使用useMemo钩子来记忆对象或数组

或者,我们可以使用
useMemo钩子来获取在渲染之间不会改变的记忆值。

应用程序.js
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钩子被无限次触发。

应用程序.js
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钩子来获取在渲染之间不会改变的记忆值。

应用程序.js
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,以获取在渲染之间不会改变的记忆值。

额外资源

您可以通过查看以下教程来了解有关相关主题的更多信息: