React报错之Too many re-renders
总览
产生"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 报错的堆栈处理
react报错 Warning: You cannot PUSH the same path using hash history 在Link上使用replace 原文地址https://reactt ...
- React报错 :browserHistory doesn't exist in react-router
由于版本问题,React中history不可用 import { hashHistory } from 'react-router' 首先应该导入react-router-dom包: import { ...
- react报错 TypeError: Cannot read property 'setState' of undefined
代码如下: class test extends Component { constructor(props) { super(props); this.state = { liked: false ...
- React报错之Cannot find name
正文从这开始~ .tsx扩展名 为了在React TypeScript中解决Cannot find name报错,我们需要在使用JSX文件时使用.tsx扩展名,在你的tsconfig.json文件中把 ...
- React报错之Cannot find namespace context
正文从这开始~ 总览 在React中,为了解决"Cannot find namespace context"错误,在你使用JSX的文件中使用.tsx扩展名,在你的tsconfig. ...
- React报错之Expected `onClick` listener to be a function
正文从这开始~ 总览 当我们为元素的onClick属性传递一个值,但是该值却不是函数时,会产生"Expected onClick listener to be a function" ...
- 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 ...
- react报错this.setState is not a function
当报错这个的时候就要看函数是否在行内绑定this,或者在constructor中绑定this. 我这里犯的错误的是虽然我在constructor中绑定了this,但是语法写的不正确. 错误示范: co ...
- React报错之useNavigate() may be used only in context of Router
正文从这开始~ 总览 当我们尝试在react router的Router上下文外部使用useNavigate 钩子时,会产生"useNavigate() may be used only i ...
- React报错:Laravel React-Router browserHistory 刷新子页面报错404
举例:myblog.com/ 刷新没问题 myblog.com/add 刷新404 browserHistory报404,hashHistory却正常 原因是少路由.web.php添加路由 Route ...
随机推荐
- Java 基础三、接口与内部类
1. 在Java程序语言中,接口是对类的一种描述.例如Arrays类中sort方法声明可以对对象进行排序,但前提是对象所属的类必须实现Comparable接口. public interface ...
- POJ1985 Cow Marathon (树的直径)
用两次dfs求出树的直径,这两次dfs可以写在一起,当然为了方便理解,这里是分开写的. 1 //两次dfs求树的重心 2 #include<cstdio> 3 #include<cs ...
- 鸟哥linux私房菜习题总结
第零章 1.请找出世界上跑得最快的超级计算机的相关信息? 系统名称:天河二号. 所在位置:中国广州中山大学超算中心. 使用的cpu型号与规格:采用的是Intel的Ivy Bridge处理器与Xeon ...
- SpringMVC访问静态资源的问题。
在项目中引用css和js文件一直出错,反复修改文件路径始终访问不到.究其原因原来是应该在web.xml 文件中添加访问静态资源的默认servlet. 如下. <servlet-mapping&g ...
- vivo互联网机器学习平台的建设与实践
vivo 互联网产品团队 - Wang xiao 随着广告和内容等推荐场景的扩展,算法模型也在不断演进迭代中.业务的不断增长,模型的训练.产出迫切需要进行平台化管理.vivo互联网机器学习平台主要业务 ...
- 动态代理(JDK、CGLIB)
JDK-Proxy(动态代理): 特点:要求被代理的对象必须接口 缺点:如果一个对象没有任何接口实现,则不能使用JDK动态代理 1.创建一个Animal 提供一个方法 2.创建一个cat类.实现Ain ...
- reportportal 集成 robotframework 自动化执行及结果可视化
前言: 最近领导想了个需求,想把目前组内在linux平台上执行的自动化脚本搞成可视化,如果是web站点相关日志可视化倒是简单了,ELK就是不错的选择,大部分可视化项目这种的,可以做的开起来很炫. 我们 ...
- Go | 基本数据类型详解
前言 基本数据类型,变量存的就是值,也叫值类型.每一种数据都定义了明确的数据类型,在内存中分配了不同大小的内存空间. Printf 和 Println 的区别 printf 输出后不换行, print ...
- SpringBoot 常用注解的原理和使用
@AutoConfiguration 读取所有jar包下的 /META-INF/spring.factories 并追加到一个 LinkedMultiValueMap 中.每一个url中记录的文件路径 ...
- Spring Boot:自定义 Whitelabel 错误页面
一.概述在本文中,我们将研究如何禁用和自定义 Spring Boot 应用程序的默认错误页面,因为正确的错误处理描述了专业性和质量工作. 2.禁用白标错误页面 首先,让我们看看如何通过将server. ...