ReactJS:最大更新深度超过错误

我试图在ReactJS中切换组件的状态,但我得到一个错误说明:

超过最大更新深度。当组件在componentWillUpdate或componentDidUpdate中反复调用setState时,就会发生这种情况。React限制了嵌套更新的数量,以防止无限循环。

我在我的代码中没有看到无限循环,有人能帮我吗?

ReactJS组件代码:

import React, { Component } from 'react';
import styled from 'styled-components';


class Item extends React.Component {
constructor(props) {
super(props);
this.toggle= this.toggle.bind(this);
this.state = {
details: false
}
}
toggle(){
const currentState = this.state.details;
this.setState({ details: !currentState });
}


render() {
return (
<tr className="Item">
<td>{this.props.config.server}</td>
<td>{this.props.config.verbose}</td>
<td>{this.props.config.type}</td>
<td className={this.state.details ? "visible" : "hidden"}>PLACEHOLDER MORE INFO</td>
{<td><span onClick={this.toggle()}>Details</span></td>}
</tr>
)}
}


export default Item;
456562 次浏览

这是因为你在渲染方法中调用toggle,这将导致重新渲染,toggle将再次调用,再次重新渲染,等等。

代码中的这一行:

{<td><span onClick={this.toggle()}>Details</span></td>}

你需要让onClick指向this.toggle,而不是调用它。

修复问题执行以下操作:

{<td><span onClick={this.toggle}>Details</span></td>}

你应该在调用函数时传递事件对象:

{<td><span onClick={(e) => this.toggle(e)}>Details</span></td>}

如果你不需要处理onClick事件,你也可以输入:

{<td><span onClick={(e) => this.toggle()}>Details</span></td>}

现在还可以在函数中添加参数。

如果你不需要向函数传递参数,只需从函数中删除(),如下所示:

<td><span onClick={this.toggle}>Details</span></td>

但是如果你想传递参数,你应该像下面这样做:

<td><span onClick={(e) => this.toggle(e,arg1,arg2)}>Details</span></td>
< p > 先忘掉react: < br > 这与react无关,让我们了解Java Script的基本概念。例如,您已经在java脚本中编写了以下函数(名称为A)
function a() {


};
< p > 问题1)如何调用我们已经定义的函数? < br > 答:一个();< / p > < p > 问题2)如何传递函数的引用,以便稍后调用它? < br > 答:让乐趣= a;

现在回到你的问题,你已经使用了函数名的paranthesis,这意味着当下面的语句将被渲染时,函数将被调用。

<td><span onClick={this.toggle()}>Details</span></td>

Then How to correct it?
Simple!! Just remove parenthesis. By this way you have given the reference of that function to onClick event. It will call back your function only when your component is clicked.

 <td><span onClick={this.toggle}>Details</span></td>

< p > 一个与反应相关的建议: < br > 避免使用有人在回答中建议的内联函数,这可能会导致性能问题。 避免下面的代码,它将在函数被调用时一次又一次地创建同一个函数的实例(lamda语句每次都会创建新实例) 注意:,不需要显式地将事件(e)传递给函数。你可以在函数中访问它而不传递它。

{<td><span onClick={(e) => this.toggle(e)}>Details</span></td>}

https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578

ReactJS:最大更新深度超过错误

inputDigit(digit){
this.setState({
displayValue: String(digit)
})


<button type="button"onClick={this.inputDigit(0)}>

为什么呢?

<button type="button"onClick={() => this.inputDigit(1)}>1</button>
onDigit函数设置状态,这将导致一个重映 导致onDigit触发,因为那是你设置的值 onClick使状态被设置,从而引起渲染, 这会导致onDigit触发,因为这是你的值…等等

onClick你应该调用函数,这叫做你的函数切换。

onClick={() => this.toggle()}

我知道这个问题有很多答案,但因为他们大多数都是老的(好吧,更老了),没有人提到我非常喜欢的方法。简而言之:

使用功能组件和钩子

长:

尽量使用尽可能多的功能组件,而不是类特别是渲染,并尽量保持他们尽可能纯粹(是的,数据是脏的默认情况下我知道)。

功能组件有两个明显的好处(还有更多):

  • 纯粹性或接近纯粹性使调试变得更加容易
  • 功能组件不需要构造函数锅炉代码

快速证明第二点-这不是绝对恶心吗?

constructor(props) {
super(props);
this.toggle= this.toggle.bind(this);
this.state = {
details: false
}
}

如果你使用功能组件进行更多的渲染,你将需要伟大的duo - hook的第二部分。为什么它们比生命周期方法更好,它们还能做什么以及更多的事情,这将占用我很多空间来介绍,所以我建议你听他自己说:丹说教钩子

在这种情况下,你只需要两个钩子:

回调钩子,方便地命名为useCallback。这样可以防止在重新渲染时反复绑定函数。

一个名为useState的状态钩子,用于在整个组件都是函数并整体执行的情况下保持状态(是的,这是可能的,因为钩子的魔力)。在该钩子中,您将存储toggle的值。

如果你读到这部分,你可能想看看我所说的一切是如何应用于原始问题的。给你: 演示 < / p >

对于那些只想看一眼组件和WTF是关于什么的人,这里是:

const Item = () => {


// HOOKZ
const [isVisible, setIsVisible] = React.useState('hidden');


const toggle = React.useCallback(() => {
setIsVisible(isVisible === 'visible' ? 'hidden': 'visible');
}, [isVisible, setIsVisible]);


// RENDER
return (
<React.Fragment>
<div style=\{\{visibility: isVisible}}>
PLACEHOLDER MORE INFO
</div>
<button onClick={toggle}>Details</button>
</React.Fragment>
)
};

PS:我写这个是为了防止很多人有类似的问题。希望他们会喜欢我在这里展示的东西,至少好到可以再谷歌一点。这不是我说其他答案是错误的,这是我说,自从它们被写出来以来,有另一种方法(恕我直言,更好的方法)来处理这个问题。

< p > 1。如果我们想在调用中传递参数,那么我们需要像下面这样调用方法 因为我们使用的是箭头函数,所以不需要在constructor中绑定方法
onClick={() => this.save(id)}

当我们像这样在构造函数中绑定方法时

this.save= this.save.bind(this);

然后,我们需要调用该方法而不传递任何参数,如下所示

onClick={this.save}

,我们尝试在调用函数时传递参数 如下所示,错误就会出现,如最大深度超出

 onClick={this.save(id)}

在本例中,就是这段代码

{<td><span onClick={this.toggle()}>Details</span></td>}

导致切换函数立即调用,并重新呈现它一次又一次,从而进行无限的调用。

因此,只传递对toggle方法的引用就可以解决问题。

所以,

{<td><span onClick={this.toggle}>Details</span></td>}

将是解决方案代码。

如果你想使用(),你应该使用一个像这样的箭头函数

{<td><span onClick={()=> this.toggle()}>Details</span></td>}

如果你想传递参数,你应该选择最后一个选项,你可以这样传递参数

{<td><span onClick={(arg)=>this.toggle(arg)}>Details</span></td>}

在最后一种情况下,它不会立即调用,也不会导致函数的重新呈现,因此避免了无限调用。

很多好答案,但都遗漏了一些例子,考虑到react/ react native中的钩子

正如上面的回答所写的那样,回调不应该是“被调用”的。在子组件中,但只被引用。

这意味着什么? 让我们考虑一个父组件,它有2个改变rgb颜色的子组件,用于在按

时改变颜色
import React, { useState } from "react"
import {View, Text, StyleSheet } from "react-native"
import ColorCounter from "../components/ColorCounter"


const SquareScreen = () =>{
const [red, setRed] = useState(0)
const [blue, setBlue] = useState(0)
const [green, setGreen] = useState(0)


return (
<View>
<ColorCounter
onIncrease={() => setRed(red + 15)}
onDecrease={() => setRed(red - 15)}
color="Red"
/>
<ColorCounter
onIncrease={() => setBlue(blue + 15)}
onDecrease={() => setBlue(blue - 15)}
color="Blue"
/>
<ColorCounter
onIncrease={() => setGreen(green + 15)}
onDecrease={() => setGreen(green - 15)}
color="Green"
/>
<View
style=\{\{
height:150,
width:150,
backgroundColor:`rgb(${red},${blue},${green})`
}}
/>
</View>
)
}


const styles = StyleSheet.create({})


export default SquareScreen

这是子按钮组件:

import React, { useState } from "react"
import {View, Text, StyleSheet, Button } from "react-native"


const ColorCounter = ({color, onIncrease, onDecrease}) =>{
return (
<View>
<Text>{color}</Text>
<Button onPress={onIncrease}  title={`Increase ${color}`} /> --> here if you use onPress={onIncrease()} this would cause a call of setColor(either setRed,SetBlue or setGreen) that call again onIncrease and so on in a loop)
<Button onPress={onDecrease}  title={`Decrease ${color}`} />
</View>
)
}


export default ColorCounter

最近我得到了这个错误:

错误:迷你反应错误#185;访问https://reactjs.org/docs/error-decoder.html?invariant=185获取完整的消息,或者使用非简化的开发环境获取完整的错误和其他有用的警告。

您刚刚遇到的错误的全文如下:

超过最大更新深度。当组件在componentWillUpdatecomponentDidUpdate中反复调用setState时,就会发生这种情况。React限制了嵌套更新的数量,以防止无限循环。

好的。这是我的案例,我使用react函数组件+ react钩子。让我们先看看错误的示例代码:

import { useEffect, useState } from "react";
const service = {
makeInfo(goods) {
if (!goods) return { channel: "" };
return { channel: goods.channel };
},
getGoods() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
channel: "so",
id: 1,
banners: [{ payway: "visa" }, { payway: "applepay" }]
});
}, 1000);
});
},
makeBanners(info, goods) {
if (!goods) return [];
return goods.banners.map((v) => {
return { ...v, payway: v.payway.toUpperCase() };
});
}
};
export default function App() {
const [goods, setGoods] = useState();
const [banners, setBanners] = useState([]);


useEffect(() => {
service.getGoods().then((res) => {
setGoods(res);
});
}, []);


const info = service.makeInfo(goods);


useEffect(() => {
console.log("[useEffect] goods: ", goods);
if (!goods) return;
setBanners(service.makeBanners({}, goods));
}, [info, goods]);


return <div>banner count: {banners.length}</div>;
}

service -进程API调用,并有一些方法转换DTO数据视图模型。这与React无关。也许你的项目中有这样的服务。

我的逻辑是,banners视图模型是从API返回的goods数据构造的。

useEffect({...}, [info, goods])有两个依赖项:infogoods

infogoods改变时,useEffect钩子将重新执行,设置横幅视图模型,看起来不错,对吗?

不!将导致内存泄漏。useEffect钩子将无限执行。为什么?

因为当setBanner()执行时,组件将重新呈现,const info = service.makeInfo(goods);语句将再次执行,返回new info对象,这将导致useEffect的deps发生变化,导致useEffect再次执行,形成死循环。

解决方案:使用useMemo返回一个记忆值。使用这个记忆值作为useEffect钩子的依赖项。

// ...
const info = useMemo(() => {
return service.makeInfo(goods);
}, [goods]);


useEffect(() => {
console.log("[useEffect] goods: ", goods);
if (!goods) return;
setBanners(service.makeBanners({}, goods));
}, [info, goods]);


//...

code andbox

当我像这样useEffect(() =>{})使用useEffect时,向它添加[],就像这样useEffect(() => {},[]). use。

在我的例子中,这是由于无限循环。

我的一个函数是设置状态,组件正在重新加载,该组件依次导致函数重新运行,然后函数再次设置状态。这个循环一直到无穷。

为了避免这种情况,我将函数包装在useEffect钩子中,并赋予它一些依赖项,以便该函数仅在依赖项更改时运行。

只需删除(),但如果是useEffect的情况,则

const [isInitialRender, setIsInitialRender] = useState(true);


useEffect(() => {
const data = localStorage.getItem("auth");
if(isInitialRender){
setIsInitialRender(false);
if (data) {
const parsed = JSON.parse(data);
setAuth({...auth, user: parsed.user, token: parsed.token });
}
}
}, [auth, isInitialRender]);

isInitialRender true和false将避免你陷入循环