总览

产生"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 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的更多相关文章

  1. react 报错的堆栈处理

    react报错 Warning: You cannot PUSH the same path using hash history 在Link上使用replace 原文地址https://reactt ...

  2. React报错 :browserHistory doesn't exist in react-router

    由于版本问题,React中history不可用 import { hashHistory } from 'react-router' 首先应该导入react-router-dom包: import { ...

  3. react报错 TypeError: Cannot read property 'setState' of undefined

    代码如下: class test extends Component { constructor(props) { super(props); this.state = { liked: false ...

  4. React报错之Cannot find name

    正文从这开始~ .tsx扩展名 为了在React TypeScript中解决Cannot find name报错,我们需要在使用JSX文件时使用.tsx扩展名,在你的tsconfig.json文件中把 ...

  5. React报错之Cannot find namespace context

    正文从这开始~ 总览 在React中,为了解决"Cannot find namespace context"错误,在你使用JSX的文件中使用.tsx扩展名,在你的tsconfig. ...

  6. React报错之Expected `onClick` listener to be a function

    正文从这开始~ 总览 当我们为元素的onClick属性传递一个值,但是该值却不是函数时,会产生"Expected onClick listener to be a function" ...

  7. React报错:Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix,

    今天在开发时报了以下错误,记录一下 我们不能在组件销毁后设置state,防止出现内存泄漏的情况 出现原因直接告诉你了,组件都被销毁了,还设置个锤子的state啊 解决方案: 利用生命周期钩子函数:co ...

  8. react报错this.setState is not a function

    当报错这个的时候就要看函数是否在行内绑定this,或者在constructor中绑定this. 我这里犯的错误的是虽然我在constructor中绑定了this,但是语法写的不正确. 错误示范: co ...

  9. React报错之useNavigate() may be used only in context of Router

    正文从这开始~ 总览 当我们尝试在react router的Router上下文外部使用useNavigate 钩子时,会产生"useNavigate() may be used only i ...

  10. React报错:Laravel React-Router browserHistory 刷新子页面报错404

    举例:myblog.com/ 刷新没问题 myblog.com/add 刷新404 browserHistory报404,hashHistory却正常 原因是少路由.web.php添加路由 Route ...

随机推荐

  1. P7962 [NOIP2021] 方差 (DP)

    题目的意思就是可以交换差分数组,对答案进行化简:n∑ai2​−(∑ai​)2 ,再通过手玩分析可得最优解的差分数组一定是单谷(可以感性理解一下),因此我们将差分数组排序,依次加入,每次可以选择加在左边 ...

  2. POJ1094 Sorting It All Out (floyd传递闭包)

    关系具有传递性,可以用floyd解决. 将关系都看做i<j的形式,令d[i][j]=1,如果d[i][j]=d[j][i]=1,说明矛盾:d[i][j]=d[j][i]=0,说明i与j的关系无法 ...

  3. JSP页面实现验证码校验

    目录 验证码校验分析 生成验证码 测试验证码 校验验证码 测试验证码校验 添加验证码刷新 在网页页面的使用中为防止"非人类"的大量操作和防止一些的信息冗余,增加验证码校验是许多网站 ...

  4. JavaScript基础&实战(3)js中的流程控制语句、条件分支语句、for循环、while循环

    文章目录 1.流程控制语句 1.1 代码 1.2 测试结果 2.弹窗提示输入内容 2.1 代码 2.2 测试结果 3.条件分支语句 3.1 代码 3.2 测试结果 4.while和 do...whil ...

  5. 齐博x1 二次开发的灵魂fun函数

    X1最强大之处,体现在灵活,扩展性强,在使用过程中,你会发现灵活之处非常之多. 现在跟大家讲一下,灵魂函数 fun() X1的核心函数文件是 application/common.php 随着模块频道 ...

  6. js判断手机系统是android还是ios?

    var u = navigator.userAgent; //识别各种浏览器 var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Ad ...

  7. day02-HTML02

    4.HTML 4.3HTML基本标签 4.3.9表格(table)标签 基本语法: <table border="边框宽度" cellspacing="空隙大小&q ...

  8. VS2022连接Oracle数据库所需包和连接字符串

    VS连接ORACLE数据库 l  VS2022连接ORACLE数据库时,需要引入Oracle.ManagedDataAccess.Core包. l  引入方式:打开VS2022==>项目==&g ...

  9. $_SERVER["REQUEST_URI"],在 PHP 众多预定义服务器变量中,$_SERVER["REQUEST_URI"] 算是经常用到的,但是这个变量只有 apache 才支持

    例如访问:http://localhost/index.php?app=lunbo获取到的$_SERVER["REQUEST_URI"]为"/index.php?app= ...

  10. Sublime Text怎样自定义配色和主题

    一.自定义配色方案 1 基础知识 配色方案[Color Scheme]文件保存在以下路径[ST安装目录]: "D:\Program Files\Sublime Text\Packages\C ...